20 нояб. 2018 г.
Всем привет! Сегодня раскажу о работе с библиотекой для отображения карт Leaflet. Примеры из статьи доступны на github: https://github.com/antonshell/leaflet_examples. Библитека может быть полезна, например, для отображения карт на сайте. В частности для отображения gpx треков. Преимущество перед google/yandex maps в том, что leaflet - это открытая библиотека и предоставляет больше возможностей для настройки/расширения. Главный недостаток в том, что придется настраивать все самому. Из коробки может выглядеть не так красиво, как google maps, зато есть множество возможностей расширения функционала.
Для работы нам понадобится d3, leaflet, плагины leaflet-elevation и leaflet-gpx, а также togeojson. Для работы одного из примеров нужен пакет xmldom. Также в примере используется jquery, хотя непосредственно для работы карт она не требуется.
Есть примеры работы gpx и elevation плагинов https://github.com/MrMufflon/Leaflet.Elevation http://mpetazzoni.github.io/leaflet-gpx/
Можно установить зависимости через npm.
npm install jquery npm install d3 npm install leaflet npm install leaflet.elevation npm install leaflet-gpx npm install @mapbox/togeojson npm install xmldom
Правда, в этом случае возможна проблема с версиями пакетов. В некоторых случаях может отвалиться, например, отображение высоты.
В данном примере для наглядности буду использовать строго определенные версии пакетов.
Подключаем скрипты и css на страницу. В данном примере использую файлы из папки lib.
<script src="lib/jquery/dist/jquery.min.js"></script> <script charset="utf-8" src="lib/d3/d3.min.js"></script> <link href="css/gpx.css" rel="stylesheet"> <link href="lib/leaflet/leaflet.css" rel="stylesheet"> <link href="lib/leaflet-elevation/dist/Leaflet.Elevation-0.0.2.css" rel="stylesheet"> <script src="lib/leaflet/leaflet.js" type="text/javascript"></script> <script src="lib/leaflet-elevation/dist/Leaflet.Elevation-0.0.2.min.js" type="text/javascript"></script> <script src="lib/leaflet-gpx/gpx.js" type="text/javascript"></script> <script src='lib/togeojson/togeojson.js'></script>
Подключаем слой карты. Для этого нужно зарегестрироваться на сервисе mapbox https://www.mapbox.com/account/ и получить token. Бесплатный аккаунт включает x показов карт в месяц.
Теперь можем отобразить карту на сайте. В качестве слоя карт используется open street maps.
<div id="map"></div> <script type="text/javascript"> $(document).ready(function() { createMap('map'); }); function createMap(elementId){ var neLat="36.79923084564507"; var neLng="21.265787724405527"; var swLat="38.496870584785938"; var swLng="24.058144837617874"; var gpx = 'gpx/greece-2018/full.gpx'; var map = new L.Map(elementId); var service = new L.tileLayer('https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token=pk.eyJ1IjoiYW50b25zaGVsbCIsImEiOiJjam41b3gzZmMwM3V5M2tueHpoanNocnZtIn0.LNyZF8tLB9G-JdW4svni1Q', { attribution: 'Map data © <a href="http://openstreetmap.org">OpenStreetMap</a> contributors, <a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>, Imagery � <a href="http://mapbox.com">Mapbox</a>', maxZoom: 18, id: 'mapbox.streets' }); var bounds = new L.LatLngBounds(new L.LatLng(neLat, neLng), new L.LatLng(swLat, swLng)); map.addLayer(service).fitBounds(bounds); } </script>
Попробуем добавить gpx трек на сайт. Будем запрашивать его с сервера по ajax. Также включим отображение высоты.
Live Demo: http://demo.antonshell.me/leaflet_examples/example_01.html
<div id="map"></div> <script type="text/javascript"> $(document).ready(function() { createMap('map'); }); function createMap(elementId){ var neLat="36.79923084564507"; var neLng="21.265787724405527"; var swLat="38.496870584785938"; var swLng="24.058144837617874"; var gpx = 'gpx/greece-2018/full.gpx'; $.ajax({ url: gpx, dataType: "xml", success: function(xml) { var map = new L.Map(elementId); var service = new L.tileLayer('https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token=pk.eyJ1IjoiYW50b25zaGVsbCIsImEiOiJjam41b3gzZmMwM3V5M2tueHpoanNocnZtIn0.LNyZF8tLB9G-JdW4svni1Q', { attribution: 'Map data © <a href="http://openstreetmap.org">OpenStreetMap</a> contributors, <a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>, Imagery � <a href="http://mapbox.com">Mapbox</a>', maxZoom: 18, id: 'mapbox.streets' }); var bounds = new L.LatLngBounds(new L.LatLng(neLat, neLng), new L.LatLng(swLat, swLng)); if(typeof xml != 'object'){ xml = $.parseXML( xml ); } geojson = toGeoJSON.gpx(xml); var el = L.control.elevation({ width: 400, height: 125 }); el.addTo(map); var gjl = L.geoJson(geojson,{ onEachFeature: el.addData.bind(el) }).addTo(map); map.addLayer(service).fitBounds(bounds); } }); } </script>
Чтобы трек gpx трек отображадся в центре карты, нужно настроить привязку карты. Для этого нам нужно получить максимальные и минимальный значения широты и долготы. Иногда эта информация содержится в метаданных трека. Если нет, то c помощью php скрипты можно обойти все координаты и найти максимальные и минимальные значения.
<?php use src\GpxBounds; require '_bootstrap.php'; $boundsService = new GpxBounds(); $gpx = __DIR__ . '/gpx/greece-2018/full.gpx'; $bounds = $boundsService->getLatLngBounds($gpx); echo $gpx . "\n"; print_r($bounds);
<?php // $gpx = 'gpx/greece-2018/full.gpx'; namespace src; use SimpleXMLElement; /** * Class GpxBounds * @package src */ class GpxBounds{ /** * @param $gpx // example: 'gpx/greece-2018/full.gpx' * @return array */ public function getLatLngBounds($gpx){ $latArray = []; $lonArray = []; $parsedGpx = $this->parseGpx($gpx); foreach($parsedGpx as $line){ if(isset($line['attributes']['LAT'])){ $latArray[] = $line['attributes']['LAT']; } if(isset($line['attributes']['LAT'])){ $lonArray[] = $line['attributes']['LON']; } } $bounds = [ 'neLat' => min($latArray), 'neLng' => min($lonArray), 'swLat' => max($latArray), 'swLng' => max($lonArray), ]; return $bounds; } /** * @param $gpxArray * @return array */ public function getLatLngBoundsArray($gpxArray) { $latArray = []; $lonArray = []; foreach ($gpxArray as $gpx){ $parsedGpx = $this->parseGpx($gpx); foreach($parsedGpx as $line){ if(isset($line['attributes']['LAT'])){ $latArray[] = $line['attributes']['LAT']; } if(isset($line['attributes']['LAT'])){ $lonArray[] = $line['attributes']['LON']; } } } $bounds = [ 'neLat' => min($latArray), 'neLng' => min($lonArray), 'swLat' => max($latArray), 'swLng' => max($lonArray), ]; return $bounds; } /** * @param $gpx * @return mixed */ private function parseGpx($gpx){ $content = file_get_contents($gpx); $parser = xml_parser_create(); xml_parse_into_struct($parser,$content,$parsedGpx); return $parsedGpx; } }
// $gpx = 'gpx/greece-2018/full.gpx'; /** * @param $gpx * @return array */ public function getLatLngBounds($gpx){ $latArray = []; $lonArray = []; $parsedGpx = $this->parseGpx($gpx); foreach($parsedGpx as $line){ if(isset($line['attributes']['LAT'])){ $latArray[] = $line['attributes']['LAT']; } if(isset($line['attributes']['LAT'])){ $lonArray[] = $line['attributes']['LON']; } } $bounds = [ 'neLat' => min($latArray), 'neLng' => min($lonArray), 'swLat' => max($latArray), 'swLng' => max($lonArray), ]; return $bounds; } /** * @param $gpx * @return mixed */ private function parseGpx($gpx){ $content = file_get_contents($gpx); $parser = xml_parser_create(); xml_parse_into_struct($parser,$content,$parsedGpx); return $parsedGpx; }
В результате получаем массив координат и можем использовать их в leaflet для привязки карты
Array ( [neLat] => 36.79923084564507 [neLng] => 21.265787724405527 [swLat] => 38.496870584785938 [swLng] => 24.058144837617874 )
Отображение маркеров на карте делается достаточно просто. Достаточно создать объект, для каждого маркера, указать текст и привязать его к карте.
Live Demo: http://demo.antonshell.me/leaflet_examples/example_06.html
var marker1 = L.marker([36.99923084564507, 21.995787724405527]).addTo(map); marker1.bindPopup("<b>Marker 1</b><br>I am a popup."); var marker2 = L.marker([37.22923084564507, 22.595787724405527]).addTo(map); marker2.bindPopup("<b>Marker 2</b><br>I am a popup.");
Маршрутные точки garmin хранятся в файлах в формате gpx. Одна точка выглядит примерно так:
<wpt lat="54.214897966012359" lon="22.155264979228377"> <ele>137.40441899999999</ele> <time>2018-08-12T11:21:16Z</time> <name>0555</name> <sym>Flag, Blue</sym> <type>user</type> <extensions> <gpxx:WaypointExtension> <gpxx:DisplayMode>SymbolAndName</gpxx:DisplayMode> </gpxx:WaypointExtension> <wptx1:WaypointExtension> <wptx1:DisplayMode>SymbolAndName</wptx1:DisplayMode> </wptx1:WaypointExtension> <ctx:CreationTimeExtension> <ctx:CreationTime>2018-08-12T11:21:16Z</ctx:CreationTime> </ctx:CreationTimeExtension> </extensions> </wpt>
Здесь нам нужны координаты, название точки и, возможно, время. На сервере с помощью php скрипта обработаем gpx файл, вытащим оттуда все данные и сохраним в json.
<?php use src\GeoJson; use src\TrackPoints; require '_bootstrap.php'; $geoJsonService = new GeoJson(); $trackpointsService = new TrackPoints(); // gpx to geojson $gpx = __DIR__ . '/gpx/poland-2018/full.gpx'; $geoJson = $geoJsonService->convertGpxToGeoJson($gpx, true, true, false); $geoJson = json_encode($geoJson, JSON_UNESCAPED_UNICODE); $geoJsonGz = gzencode($geoJson); $dirPath = __DIR__ . '/output/geojson/poland-2018'; if(!is_dir($dirPath)){ mkdir($dirPath); } file_put_contents($dirPath . '/full.geoJson', $geoJson); file_put_contents($dirPath . '/full.geoJson.gz', $geoJsonGz); // trackPoints $gpx = __DIR__ . '/gpx/poland-2018/points.gpx'; $trackPoints = $trackpointsService->parseGpxTrackPoints($gpx); $trackPoints = json_encode($trackPoints, JSON_UNESCAPED_UNICODE); $trackPointsGz = gzencode($trackPoints); file_put_contents($dirPath . '/trackPoints.json', $trackPoints); file_put_contents($dirPath . '/trackPoints.json.gz', $trackPointsGz); echo "Job is done\n";
<?php namespace src; /** * Class TrackPoints * @package src */ class TrackPoints{ /** * @param $gpx * @return array */ public function parseGpxTrackPoints($gpx){ $parsedGpx = $this->parseGpx($gpx); $data = ['bounds' => [], 'points' => []]; if(isset($parsedGpx['metadata']['bounds']['@attributes'])){ $data['bounds'] = $parsedGpx['metadata']['bounds']['@attributes']; } $points = []; foreach ($parsedGpx['wpt'] as $item){ $points[] = [ 'lat' => $item['@attributes']['lat'], 'lon' => $item['@attributes']['lon'], 'elevation' => $item['ele'] ?? '', 'time' => $item['time'] ?? '', 'name' => $item['name'] ?? 'No name', ]; } $data['points'] = $points; return $data; } /** * @param $gpx * @return mixed */ private function parseGpx($gpx){ $content = file_get_contents($gpx); $xml = simplexml_load_string($content); $json = json_encode($xml); $parsedGpx = json_decode($json,TRUE); return $parsedGpx; } }
Затем в бразуере через ajax получим json файл и
var trackPointsUrl = 'output/full.php?file=poland-2018/trackPoints.json.gz'; // track points $.ajax({ url: trackPointsUrl, success: function(trackPoints) { var points = trackPoints['points']; for(var index in points){ if(points.hasOwnProperty(index)){ var point = points[index]; var lat = point['lat']; var lon = point['lon']; var name = point['name']; var marker1 = L.marker([lat, lon]).addTo(map); marker1.bindPopup(name); } } } });
На карте отображаются маршрутные точки.
Live Demo: http://demo.antonshell.me/leaflet_examples/example_08.html
В результате у нас получилась карта на которой отображается gpx трек, высоты для него и маршрутные точки.
В следующей статье рассмотрим конкатенацию gpx, подключение множественных треков, отображение марщрутных точек и оптимизацию производительности. Код примеров доступен на github: https://github.com/antonshell/leaflet_examples На этом пока все. Спасибо за внимание!