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 На этом пока все. Спасибо за внимание!