openstreetmap-server-ubuntu-16-04+GraphHopper搭建离线地图服务器和离线路径规划

  • 一.升级系统
  • 二.安装PostgreSQL数据库和PostGIS扩展
  • 三.下载地图样式表和上传地图数据
  • 四.将地图数据导入PostgresSQL
  • 五.安装mapnik mod_tile
  • 六.添加中文支持
  • 七.生成Mapnik Stylesheet
  • 八.配置渲染文件
  • 九.配置Apache服务器
  • 十.使用Leftlet在网页上显示和操作地图
  • 十一.GraphHopper的安装
  • 十二.编写leaftlet文件

一.升级系统

sudo apt update
sudo apt upgrade

二.安装PostgreSQL数据库和PostGIS扩展

sudo apt install postgresql-9.5
sudo apt install postgresql-contrib
sudo apt install postgis postgresql-9.5-postgis-2.2

安装语言

export LANGUAGE="en_US.UTF-8"
export LANG="en_US.UTF-8"
export LC_ALL="en_US.UTF-8"
sudo locale-gen en_US.UTF-8
sudo dpkg-reconfigure locales

ubuntu下postgres用户并登录PostgreSQL服务器:

sudo -u postgres -i

创建用户osm

createuser osm

然后创建一个名为gis的数据库,同时将osm作为数据库的所有者,-E UTF8指定要在数据库中使用的字符编码方案是UTF8:

createdb -E UTF8 -O osm gis

接下来,为gis数据库创建postgis和hstore扩展:

psql -c "CREATE EXTENSION postgis;" -d gis
psql -c "CREATE EXTENSION hstore;" -d gis

将osm设置为表所有者:

psql -c "ALTER TABLE spatial_ref_sys OWNER TO osm;" -d gis

退出postgres用户:

exit;

创建ubuntu的osm用户便后续使用:

sudo adduser osm

三.下载地图样式表和上传地图数据

切换osm用户:

su - osm

将最新的CartoCSS地图样式表下载到osm用户的主目录

wget https://github.com/gravitystorm/openstreetmap-carto/archive/v4.20.0.tar.gz

解压:

tar -zxvf v4.20.0.tar.gz

上传地图数据到osm用户的目录,我使用的是上海市地图数据:
各个省市osm数据下载地址

退出用户

exit

四.将地图数据导入PostgresSQL

需安装工具:osm2pgsql

sudo apt install osm2pgsql
su - osm

运行以下命令以将地图样式表和地图数据加载到gis数据库中,用你自己的地图数据文件替换

osm2pgsql --slim -d gis --hstore --multi-geometry --number-processes 8 --tag-transform-script /home/osm/openstreetmap-carto-4.20.0/openstreetmap-carto.lua --style /home/osm/openstreetmap-carto-4.20.0/openstreetmap-carto.style /home/osm/shanghai-latest.osm.pbf

退出用户

exit

相关说明:
–username osm:指定数据库用户。
–slim:以苗条模式而不是普通模式运行,如果要在将来使用OSM更改文件(OSC)更新地图数据,则需要此选项。
-d gis:选择数据库。
–hstore:将不带列的标记添加到PostgreSQL表的其他hstore(键/值)列。
–multi-geometry:在postgresql表中生成多几何特征。
–style:指定样式文件的位置。
–number-processes:服务器上的CPU核心数。
-C选项以兆字节为单位指定缓存大小,较大的缓存大小导致更快的导入速度,但你需要有足够的RAM来使用缓存
最后,需要指定地图数据文件的位置。

五.安装mapnik mod_tile

sudo apt install git
sudo apt install autoconf
sudo apt install libtool
sudo apt install libmapnik-dev
sudo apt install apache2-dev

clone 仓库

sudo git clone https://github.com/openstreetmap/mod_tile.git
cd mod_tile/

编译 安装

sudo apt-get install -y g++
sudo ./autogen.sh
sudo ./configure
sudo make
sudo make install
sudo make install-mod_tile

运行结果:
Libraries have been installed in:
/usr/lib/apache2/modules

六.添加中文支持

sudo apt-get -y install fonts-wqy-zenhei
sudo apt-get install unifont
sudo apt-get install ttf-unifont

七.生成Mapnik Stylesheet

sudo apt install curl
sudo apt install unzip
sudo apt install  gdal-bin
sudo apt install mapnik-utils
sudo npm install -g carto

切换至osm用户,创建data文件夹用于存放下载的边界文件

su - osm
cd openstreetmap-carto-4.20.0
mkdir data
scripts/get-shapefiles.py(下载失败的话可以手动下载,下载链接在get-shapefiles.py里可以找到,一共六个边界包,放入data目录里)


构建mapnik xml 样式文件

carto project.mml > style.xml

会看见有一个style.xml的文件,里面是这样的

退出osm用户

 exit

报错的话需要升级你的nodejs版本链接。

八.配置渲染文件

编辑渲染配置文件。

sudo vim /usr/local/etc/renderd.conf

在[default]下,修改xml和host值。
XML=/home/osm/openstreetmap-carto-4.20.0/style.xml
HOST=localhost

在[mapnik]下,修改plugins_dir值。
plugins_dir=/usr/lib/mapnik/3.0/input/
保存文件。

安装渲染初始化脚本。

sudo cp mod_tile/debian/renderd.init /etc/init.d/renderd

授予执行权限。

sudo chmod a+x /etc/init.d/renderd

编辑初始化脚本。

sudo vim /etc/init.d/renderd

修改下面的变量值。
DAEMON=/usr/local/bin/$NAME
DAEMON_ARGS="-c /usr/local/etc/renderd.conf"
RUNASUSER=osm

保存文件。

创建下面的文件,设置osm为所有者。

sudo mkdir -p /var/lib/mod_tile
sudo chown osm:osm /var/lib/mod_tile

开始渲染服务。

sudo systemctl daemon-reload
sudo systemctl start renderd
sudo systemctl enable renderd

也可手动启动渲染服务

sudo -u 'osm' renderd -f -c /usr/local/etc/renderd.conf

九.配置Apache服务器

安装apache web server

sudo apt install apache2

创建模块加载文件。

sudo vim /etc/apache2/mods-available/mod_tile.load

写入下面的命令。
LoadModule tile_module /usr/lib/apache2/modules/mod_tile.so
创建链接

sudo ln -s /etc/apache2/mods-available/mod_tile.load /etc/apache2/mods-enabled/

之后编辑默认虚拟主机文件。

sudo vim /etc/apache2/sites-enabled/000-default.conf

在<VirtualHost *:80>下粘贴下面语句。
LoadTileConfigFile /usr/local/etc/renderd.conf
ModTileRenderdSocketName /var/run/renderd/renderd.sock
#Timeout before giving up for a tile to be rendered
ModTileRequestTimeout 0
#Timeout before giving up for a tile to be rendered that is otherwise missing
ModTileMissingRequestTimeout 30

保存并重启apache

sudo systemctl restart apache2

用你的浏览器访问:

your-server-ip/osm_tiles/0/0/0.png

这里可以看到世界地图

十.使用Leftlet在网页上显示和操作地图

要使用Leftlet显示你的地图,需要下载JavaScript和CSS并将其解压缩到Web根文件夹:

cd /var/www/htmlsudo wget http://cdn.leafletjs.com/leaflet/v1.4.0/leaflet.zipsudo unzip leaflet.zip

接下来,创建map.html文件:

sudo vim /var/www/html/map.html

将以下HTML代码粘贴到文件中,替换并根据需要调整经度,纬度和缩放级别:

<html><head><meta charset="UTF-8"><title>My first osm</title><link rel="stylesheet" type="text/css" href="leaflet.css"/><script type="text/javascript" src="leaflet.js"></script><style>#map{width:100%;height:100%}</style></head><body><div id="map"></div><script>var map = L.map('map').setView([0,0],5);L.tileLayer('http://你的ip/osm_tiles/{z}/{x}/{y}.png',{maxZoom:18}).addTo(map);</script></body></html>

保存并关闭文件,现在,可以通过在浏览器中输入服务器IP地址来查看你的slippy地图:
http://你的ip/map.html

预渲染

即时渲染切片会增加Web浏览器中的地图加载时间,要预渲染切片而不是动态渲染,请使用以下render_list命令,使用-z和-Z选项指定缩放级别,并根据服务器上的CPU核心数替换线程数, Render_list通过向渲染守护程序发送请求来呈现地图图块列表,预渲染的切片将缓存在/var/lib/mod_tile目录中:

render_list -m default -a -z 0 -Z 12 --num-threads=8

以上就是离线地图服务器的搭建,参考文章:
https://www.linuxbabe.com/linux-server/openstreetmap-tile-server-ubuntu-16-04

下面我们来配置离线的路径规划。我们需要用到GraphHopper来实现。

十一.GraphHopper的安装

下载下来解压并重命名为graphhopper
GitHub地址

我们还需要一个jar文件,一个yml文件,和一个osm文件:
graphhopper-web-0.13.0.jar
jar包放在一个自己新建的目录里
config-example.yml
把页面的内容都复制下来,在之前的目录新建一个名为config-example.yml的文件,粘贴进去。然后修改一下里面的参数:
# for security reasons bind to localhost下,修改bindHost: localhost,把localhost修改成你的ip这样在外部就能访问了。如果端口冲突也是在这里修改端口,默认是8989。

osm文件就用之前导入的地图文件即可。都放在同一个目录下。

最后执行下面的命令:

java -Xmx1g -Xms1g-Dgraphhopper.datareader.file=shanghai-latest.osm.pbf -jar *.jar server config-example.yml


最后访问你的页面,端口8989
http://你的ip:8989

GitHub快速入门文档

十二.编写leaftlet文件

在/var/www/html下新建一个html文件,例如test.html

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>上海地图</title><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0"><link rel="stylesheet" href="leaflet.css" /><link rel="stylesheet" href="leaflet.contextmenu.css"/><script src="leaflet.js"></script><script src="leaflet-ant-path.js" type="text/javascript"></script><script src="leaflet.contextmenu.js"></script>
</head><style>* { margin: 0; padding: 0; }html, body { height: 100%; }#mapid { width:100%; height:100%; }
</style><body>
<div id="mapid" ></div>
<script>var map = L.map('mapid', {center: [121,31],zoom: 13,crs: L.CRS.EPSG3857,layers: [L.tileLayer('http://你的ip/osm_tiles/{z}/{x}/{y}.png', {attribution: '&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'})],contextmenu: true,contextmenuItems: [{text: '设置为起点/n',callback: setStartPoint}, {text: '设置为中间点/n',callback: setWaypoints}, '-', {text: '设置为终点/n',callback: setStopPoint}, {text: '开始规划',callback: calcRoute}]});var _startPoint,_stopPoint,_wayPoints = [];var _points = [];//用于存储所有点function setStartPoint(event){_startPoint = event.latlng;var _icon = L.icon({iconUrl:'start.png'})L.marker(_startPoint,{icon:_icon}).addTo(map);}function setWaypoints(event){_wayPoints.push(event.latlng);var _icon = L.icon({iconUrl:'way.png'})L.marker(event.latlng,{icon:_icon}).addTo(map);}function setStopPoint(event){_stopPoint = event.latlng;var _icon = L.icon({iconUrl:'stop.png'})L.marker(_stopPoint,{icon:_icon}).addTo(map);}function calcRoute() {var url = _buildRouteUrl();if(url == null || url == undefined){return;}else{var request,me = this;if (window.XMLHttpRequest) {request = new XMLHttpRequest();} else {request = new ActiveXObject('Microsoft.XMLHTTP');}request.onreadystatechange = function () { // 状态发生变化时,函数被回调if (request.readyState === 4) { // 成功完成// 判断响应结果:if (request.status === 200) {// 成功,通过responseText拿到响应的文本:return _success(request.responseText);} else {// 失败,根据响应码判断失败原因:return _fail(request.status);}} else {// HTTP请求还在继续...}}// 发送请求:request.open('GET', url);request.send();//alert('请求已发送,请等待响应...');}}function _buildRouteUrl(){var wayPoints = _setPointsSequence();if(wayPoints == null || wayPoints == undefined){return;}else{var locs = [],i,baseUrl;for (i = 0; i < wayPoints.length; i++) {locs.push('point=' + wayPoints[i].lat + ',' + wayPoints[i].lng);}baseUrl = 'http://你的ip:8989' + '/route?' + locs.join('&');return baseUrl +  '&type=json&locale=zh-CN&vehicle=car&weighting=fastest&points_encoded=false';}}function _setPointsSequence(){var me = this;if(this._startPoint == null || this._startPoint == undefined){alert("请先设置起点");return;}if(this._stopPoint == null || this._stopPoint == undefined){alert("请先设置终点");return;}this._points.push(this._startPoint);if(this._wayPoints.length > 0){for(let i=0;i<me._wayPoints.length;i++){me._points.push(me._wayPoints[i]);}}this._points.push(this._stopPoint);return this._points;}function _success(text){this._responseResult = text;var json = JSON.parse(text);var lnglats = json.paths[0].points.coordinates;//(lng,lat)var latlngs = [];//(lat,lng)for(let j=0;j<lnglats.length;j++){var lnglat = lnglats[j];var latlng = [];latlng[0] = lnglat[1];latlng[1] = lnglat[0];latlngs.push(latlng);}var path = L.polyline.antPath(latlngs,{color: "#A52A2A", pulseColor: "#0000FF"});path.addTo(map);return text;}function _fail(code){alert('请求失败,' + 'Error code: ' + code);return code;}</script>
</body>
</html>

这里用到了两个额外的插件:
leaflet.contextmenu.css

.leaflet-contextmenu {display: none;box-shadow: 0 1px 7px rgba(0,0,0,0.4);-webkit-border-radius: 4px;border-radius: 4px;padding: 4px 0;background-color: #fff;cursor: default;-webkit-user-select: none;-moz-user-select: none;user-select: none;
}.leaflet-contextmenu a.leaflet-contextmenu-item {display: block;color: #222;font-size: 12px;line-height: 20px;text-decoration: none;padding: 0 12px;border-top: 1px solid transparent;border-bottom: 1px solid transparent;cursor: default;outline: none;
}.leaflet-contextmenu a.leaflet-contextmenu-item-disabled {opacity: 0.5;
}.leaflet-contextmenu a.leaflet-contextmenu-item.over {background-color: #f4f4f4;border-top: 1px solid #f0f0f0;border-bottom: 1px solid #f0f0f0;
}.leaflet-contextmenu a.leaflet-contextmenu-item-disabled.over {background-color: inherit;border-top: 1px solid transparent;border-bottom: 1px solid transparent;
}.leaflet-contextmenu-icon {margin: 2px 8px 0 0;width: 16px;height: 16px;float: left;border: 0;
}.leaflet-contextmenu-separator {border-bottom: 1px solid #ccc;margin: 5px 0;
}

leaflet.contextmenu.js

/*Leaflet.contextmenu, a context menu for Leaflet.(c) 2015, Adam Ratcliffe, GeoSmart Maps Limited@preserve
*/(function(factory) {// Packaging/modules magic dancevar L;if (typeof define === 'function' && define.amd) {// AMDdefine(['leaflet'], factory);} else if (typeof module === 'object' && typeof module.exports === 'object') {// Node/CommonJSL = require('leaflet');module.exports = factory(L);} else {// Browser globalsif (typeof window.L === 'undefined') {throw new Error('Leaflet must be loaded first');}factory(window.L);}
})(function(L) {
L.Map.mergeOptions({contextmenuItems: []
});L.Map.ContextMenu = L.Handler.extend({_touchstart: L.Browser.msPointer ? 'MSPointerDown' : L.Browser.pointer ? 'pointerdown' : 'touchstart',statics: {BASE_CLS: 'leaflet-contextmenu'},initialize: function (map) {L.Handler.prototype.initialize.call(this, map);this._items = [];this._visible = false;var container = this._container = L.DomUtil.create('div', L.Map.ContextMenu.BASE_CLS, map._container);container.style.zIndex = 10000;container.style.position = 'absolute';if (map.options.contextmenuWidth) {container.style.width = map.options.contextmenuWidth + 'px';}this._createItems();L.DomEvent.on(container, 'click', L.DomEvent.stop).on(container, 'mousedown', L.DomEvent.stop).on(container, 'dblclick', L.DomEvent.stop).on(container, 'contextmenu', L.DomEvent.stop);},addHooks: function () {var container = this._map.getContainer();L.DomEvent.on(container, 'mouseleave', this._hide, this).on(document, 'keydown', this._onKeyDown, this);if (L.Browser.touch) {L.DomEvent.on(document, this._touchstart, this._hide, this);}this._map.on({contextmenu: this._show,mousedown: this._hide,movestart: this._hide,zoomstart: this._hide}, this);},removeHooks: function () {var container = this._map.getContainer();L.DomEvent.off(container, 'mouseleave', this._hide, this).off(document, 'keydown', this._onKeyDown, this);if (L.Browser.touch) {L.DomEvent.off(document, this._touchstart, this._hide, this);}this._map.off({contextmenu: this._show,mousedown: this._hide,movestart: this._hide,zoomstart: this._hide}, this);},showAt: function (point, data) {if (point instanceof L.LatLng) {point = this._map.latLngToContainerPoint(point);}this._showAtPoint(point, data);},hide: function () {this._hide();},addItem: function (options) {return this.insertItem(options);},insertItem: function (options, index) {index = index !== undefined ? index: this._items.length;var item = this._createItem(this._container, options, index);this._items.push(item);this._sizeChanged = true;this._map.fire('contextmenu.additem', {contextmenu: this,el: item.el,index: index});return item.el;},removeItem: function (item) {var container = this._container;if (!isNaN(item)) {item = container.children[item];}if (item) {this._removeItem(L.Util.stamp(item));this._sizeChanged = true;this._map.fire('contextmenu.removeitem', {contextmenu: this,el: item});return item;}return null;},removeAllItems: function () {var items = this._container.children,item;while (items.length) {item = items[0];this._removeItem(L.Util.stamp(item));}return items;},hideAllItems: function () {var item, i, l;for (i = 0, l = this._items.length; i < l; i++) {item = this._items[i];item.el.style.display = 'none';}},showAllItems: function () {var item, i, l;for (i = 0, l = this._items.length; i < l; i++) {item = this._items[i];item.el.style.display = '';}},setDisabled: function (item, disabled) {var container = this._container,itemCls = L.Map.ContextMenu.BASE_CLS + '-item';if (!isNaN(item)) {item = container.children[item];}if (item && L.DomUtil.hasClass(item, itemCls)) {if (disabled) {L.DomUtil.addClass(item, itemCls + '-disabled');this._map.fire('contextmenu.disableitem', {contextmenu: this,el: item});} else {L.DomUtil.removeClass(item, itemCls + '-disabled');this._map.fire('contextmenu.enableitem', {contextmenu: this,el: item});}}},isVisible: function () {return this._visible;},_createItems: function () {var itemOptions = this._map.options.contextmenuItems,item,i, l;for (i = 0, l = itemOptions.length; i < l; i++) {this._items.push(this._createItem(this._container, itemOptions[i]));}},_createItem: function (container, options, index) {if (options.separator || options === '-') {return this._createSeparator(container, index);}var itemCls = L.Map.ContextMenu.BASE_CLS + '-item',cls = options.disabled ? (itemCls + ' ' + itemCls + '-disabled') : itemCls,el = this._insertElementAt('a', cls, container, index),callback = this._createEventHandler(el, options.callback, options.context, options.hideOnSelect),icon = this._getIcon(options),iconCls = this._getIconCls(options),html = '';if (icon) {html = '<img class="' + L.Map.ContextMenu.BASE_CLS + '-icon" src="' + icon + '"/>';} else if (iconCls) {html = '<span class="' + L.Map.ContextMenu.BASE_CLS + '-icon ' + iconCls + '"></span>';}el.innerHTML = html + options.text;el.href = '#';L.DomEvent.on(el, 'mouseover', this._onItemMouseOver, this).on(el, 'mouseout', this._onItemMouseOut, this).on(el, 'mousedown', L.DomEvent.stopPropagation).on(el, 'click', callback);if (L.Browser.touch) {L.DomEvent.on(el, this._touchstart, L.DomEvent.stopPropagation);}// Devices without a mouse fire "mouseover" on tap, but never “mouseout"if (!L.Browser.pointer) {L.DomEvent.on(el, 'click', this._onItemMouseOut, this);}return {id: L.Util.stamp(el),el: el,callback: callback};},_removeItem: function (id) {var item,el,i, l, callback;for (i = 0, l = this._items.length; i < l; i++) {item = this._items[i];if (item.id === id) {el = item.el;callback = item.callback;if (callback) {L.DomEvent.off(el, 'mouseover', this._onItemMouseOver, this).off(el, 'mouseover', this._onItemMouseOut, this).off(el, 'mousedown', L.DomEvent.stopPropagation).off(el, 'click', callback);if (L.Browser.touch) {L.DomEvent.off(el, this._touchstart, L.DomEvent.stopPropagation);}if (!L.Browser.pointer) {L.DomEvent.on(el, 'click', this._onItemMouseOut, this);}}this._container.removeChild(el);this._items.splice(i, 1);return item;}}return null;},_createSeparator: function (container, index) {var el = this._insertElementAt('div', L.Map.ContextMenu.BASE_CLS + '-separator', container, index);return {id: L.Util.stamp(el),el: el};},_createEventHandler: function (el, func, context, hideOnSelect) {var me = this,map = this._map,disabledCls = L.Map.ContextMenu.BASE_CLS + '-item-disabled',hideOnSelect = (hideOnSelect !== undefined) ? hideOnSelect : true;return function (e) {if (L.DomUtil.hasClass(el, disabledCls)) {return;}if (hideOnSelect) {me._hide();}if (func) {func.call(context || map, me._showLocation);}me._map.fire('contextmenu.select', {contextmenu: me,el: el});};},_insertElementAt: function (tagName, className, container, index) {var refEl,el = document.createElement(tagName);el.className = className;if (index !== undefined) {refEl = container.children[index];}if (refEl) {container.insertBefore(el, refEl);} else {container.appendChild(el);}return el;},_show: function (e) {this._showAtPoint(e.containerPoint, e);},_showAtPoint: function (pt, data) {if (this._items.length) {var map = this._map,layerPoint = map.containerPointToLayerPoint(pt),latlng = map.layerPointToLatLng(layerPoint),event = L.extend(data || {}, {contextmenu: this});this._showLocation = {latlng: latlng,layerPoint: layerPoint,containerPoint: pt};if (data && data.relatedTarget){this._showLocation.relatedTarget = data.relatedTarget;}this._setPosition(pt);if (!this._visible) {this._container.style.display = 'block';this._visible = true;}this._map.fire('contextmenu.show', event);}},_hide: function () {if (this._visible) {this._visible = false;this._container.style.display = 'none';this._map.fire('contextmenu.hide', {contextmenu: this});}},_getIcon: function (options) {return L.Browser.retina && options.retinaIcon || options.icon;},_getIconCls: function (options) {return L.Browser.retina && options.retinaIconCls || options.iconCls;},_setPosition: function (pt) {var mapSize = this._map.getSize(),container = this._container,containerSize = this._getElementSize(container),anchor;if (this._map.options.contextmenuAnchor) {anchor = L.point(this._map.options.contextmenuAnchor);pt = pt.add(anchor);}container._leaflet_pos = pt;if (pt.x + containerSize.x > mapSize.x) {container.style.left = 'auto';container.style.right = Math.min(Math.max(mapSize.x - pt.x, 0), mapSize.x - containerSize.x - 1) + 'px';} else {container.style.left = Math.max(pt.x, 0) + 'px';container.style.right = 'auto';}if (pt.y + containerSize.y > mapSize.y) {container.style.top = 'auto';container.style.bottom = Math.min(Math.max(mapSize.y - pt.y, 0), mapSize.y - containerSize.y - 1) + 'px';} else {container.style.top = Math.max(pt.y, 0) + 'px';container.style.bottom = 'auto';}},_getElementSize: function (el) {var size = this._size,initialDisplay = el.style.display;if (!size || this._sizeChanged) {size = {};el.style.left = '-999999px';el.style.right = 'auto';el.style.display = 'block';size.x = el.offsetWidth;size.y = el.offsetHeight;el.style.left = 'auto';el.style.display = initialDisplay;this._sizeChanged = false;}return size;},_onKeyDown: function (e) {var key = e.keyCode;// If ESC pressed and context menu is visible hide itif (key === 27) {this._hide();}},_onItemMouseOver: function (e) {L.DomUtil.addClass(e.target || e.srcElement, 'over');},_onItemMouseOut: function (e) {L.DomUtil.removeClass(e.target || e.srcElement, 'over');}
});L.Map.addInitHook('addHandler', 'contextmenu', L.Map.ContextMenu);
L.Mixin.ContextMenu = {bindContextMenu: function (options) {L.setOptions(this, options);this._initContextMenu();return this;},unbindContextMenu: function (){this.off('contextmenu', this._showContextMenu, this);return this;},addContextMenuItem: function (item) {this.options.contextmenuItems.push(item);},removeContextMenuItemWithIndex: function (index) {var items = [];for (var i = 0; i < this.options.contextmenuItems.length; i++) {if (this.options.contextmenuItems[i].index == index){items.push(i);}}var elem = items.pop();while (elem !== undefined) {this.options.contextmenuItems.splice(elem,1);elem = items.pop();}},replaceContextMenuItem: function (item) {this.removeContextMenuItemWithIndex(item.index);this.addContextMenuItem(item);},_initContextMenu: function () {this._items = [];this.on('contextmenu', this._showContextMenu, this);},_showContextMenu: function (e) {var itemOptions,data, pt, i, l;if (this._map.contextmenu) {data = L.extend({relatedTarget: this}, e);pt = this._map.mouseEventToContainerPoint(e.originalEvent);if (!this.options.contextmenuInheritItems) {this._map.contextmenu.hideAllItems();}for (i = 0, l = this.options.contextmenuItems.length; i < l; i++) {itemOptions = this.options.contextmenuItems[i];this._items.push(this._map.contextmenu.insertItem(itemOptions, itemOptions.index));}this._map.once('contextmenu.hide', this._hideContextMenu, this);this._map.contextmenu.showAt(pt, data);}},_hideContextMenu: function () {var i, l;for (i = 0, l = this._items.length; i < l; i++) {this._map.contextmenu.removeItem(this._items[i]);}this._items.length = 0;if (!this.options.contextmenuInheritItems) {this._map.contextmenu.showAllItems();}}
};var classes = [L.Marker, L.Path],defaultOptions = {contextmenu: false,contextmenuItems: [],contextmenuInheritItems: true},cls, i, l;for (i = 0, l = classes.length; i < l; i++) {cls = classes[i];// L.Class should probably provide an empty options hash, as it does not test// for it here and add if neededif (!cls.prototype.options) {cls.prototype.options = defaultOptions;} else {cls.mergeOptions(defaultOptions);}cls.addInitHook(function () {if (this.options.contextmenu) {this._initContextMenu();}});cls.include(L.Mixin.ContextMenu);
}
return L.Map.ContextMenu;
});

leaflet-ant-path.js

npm install --save leaflet-ant-path

会有一个node_modules的文件夹,里面有所需要的文件,复制到相应地方就行了。

最后访问你的html页面
http://你的ip//test.html
右键进行起点终点设置,右键开始规划。

最终效果图

坐标的图片需要自己添加。

这里参考了另外一位博主的文章:
https://blog.csdn.net/wml00000/article/details/84108694

openstreetmap-server-ubuntu-16-04+GraphHopper搭建离线地图服务器和离线路径规划相关推荐

  1. Ubuntu 16.04上搭建CDH5.16.1集群

    本文参考自:<Ubuntu16.04上搭建CDH5.14集群> 1.准备三台(CDH默认配置为三台)安装Ubuntu 16.04.4 LTS系统的服务器,假设ip地址分布为 192.168 ...

  2. Ubuntu 16.04下搭建intel NCS 2开发环境OpenVINO

    材料准备 Ubuntu 16.04电脑(x86_64架构),需要联网 intel NCS 2设备 intel Distribution of OpenVINO toolkit for Linux(20 ...

  3. 如何保护Ubuntu 16.04上的NGINX Web服务器

    什么是 Let's Encrypt Let's Encrypt 是互联网安全研究组织 (ISRG) 提供的免费证书认证机构.它提供了一种轻松自动的方式来获取免费的 SSL/TLS 证书 - 这是在 W ...

  4. Ubuntu 16.04下部署Graylog日志服务器

    Graylog 是一个开源的日志管理系统,集中式收集.索引.分析其它服务器发来的日志.它是由 Java 语言编写的,能够接收 TCP.UDP.AMQP 协议发送的日志信息,并且使用 Mongodb 做 ...

  5. 离线地图模式下实现路径规划

    一.根据起点.终点计算最短行驶路径(调用本地接口进行路径数据返回) 路径规划支持本地部署,路网数据标准WGS-84坐标,其他地图坐标系可通过离线坐标系转换函数进行转换后调用,返回道路经纬度也是WGS- ...

  6. 怎样在Ubuntu 14.04中搭建gitolite git服务器

     1.   首先这里我们安装openssh-serveropenssh-client,如果你用的是VPS之类的一般都默认安装好了,不过运行一个这个命令不会有错的,如果有安装就会提示已安装. sud ...

  7. Ubuntu 12.04下搭建Web服务器 (MySQL+PHP+Apache)(转)

    看了网上很多关于用linux操作系统搭建网站服务器的教程,于是我自己也测试了很多,但今天所测试的 Ubuntu 12.04下搭建Web网站服务器 (MySQL+PHP+Apache环境),感觉这个适合 ...

  8. ubuntu 16.04 源码安装samba并且配置

    好久没发博客了,毕业季整天玩哈哈哈 最近有需求要在ubuntu 16.04 上搭一个samba服务器,要求用源码 环境: VMware workstation上的ubuntu 16.04   ,   ...

  9. ubuntu 16.04笔记本合盖不休眠

    ubuntu 16.04笔记本合盖不休眠 安装 ubuntu 16.04 笔记本,通常当做服务器做一下开发,不需要进入界面,只是后台进入操作.samba映射到window主机访问,所以将笔记本合盖放置 ...

最新文章

  1. ubuntu php7 memcache,linux ubuntu下安装php memcache扩展
  2. js中 replace(/\//g, '') 什么作用. 正则表达式
  3. java itemcf_大规模电商推荐数据分析-基于ItemCF的召回
  4. 多重判定系数怎么求_关于多重共线性
  5. java.exe 安装程序_java实现可安装的exe程序实例详解
  6. Hadoop的HA机制
  7. 【Kotlin】我的第一个 Kotlin 程序
  8. 锂电池接线方法图_锂电池制浆工艺(2)——制浆设备种类及特征
  9. 如何提升应用程序启动权限
  10. LDA主题模型原理解析与python实现
  11. python读取数据库数据释放内存_在使用python处理数据时,为什么其内存无法自动释放掉?...
  12. java diamond 有什么用_Diamond语法何时在Java 8中不起作用?
  13. “飞扬小字典” -- 辅助记忆的小工具
  14. ARP报文的存入条件和回复条件
  15. 为什么恍然大悟与知识管理的几个感触:人艰不拆
  16. 阿里云服务器是否限制流量?阿里云固定宽带和按使用流量计费2种方式解读
  17. 史上最全4S店维修潜规则 看完绝不被坑
  18. ZOJ 3703 Happy Programming Contest 0-1背包 DP
  19. 新型超级电容/法拉电容介绍
  20. 【day08~10】Java面向对象基础认识

热门文章

  1. 第一部分:基础篇(一)
  2. pb导入excel文件
  3. WFP实现侧边栏导航菜单
  4. php实现微博话题 功能,PHP实现微博的@好友和话题功能
  5. 笔记 | 制作windows10装机U盘,换固态硬盘,加内存条
  6. 第三方支付(微信支付)支付流程分析
  7. 修复版拼团商城前端+后端微信小程序源码下载
  8. 充电宝建议买多大的?充电宝有多大的
  9. js获取服务器响应时间,【JS】浏览器所允许的http请求最长的响应时间?
  10. 船长的error笔记