Compare commits
No commits in common. "master" and "feature/trainlines" have entirely different histories.
master
...
feature/tr
4
.gitignore
vendored
4
.gitignore
vendored
@ -1 +1,3 @@
|
|||||||
*.user
|
htdocs/tiles/**/
|
||||||
|
scripts/geojson/*.json
|
||||||
|
scripts/geojson/**/*.json
|
||||||
|
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
[submodule "lifo-dijkstraserv"]
|
||||||
|
path = lifo-dijkstraserv
|
||||||
|
url = https://notsyncing.net:8080/markus/lifo-dijkstraserv.git
|
@ -1,24 +0,0 @@
|
|||||||
cmake_minimum_required(VERSION 3.5)
|
|
||||||
|
|
||||||
project(lifo-dijkstraserv LANGUAGES C)
|
|
||||||
#set(CMAKE_BUILD_TYPE Release)
|
|
||||||
set(CMAKE_C_FLAGS "-Wall")
|
|
||||||
|
|
||||||
find_package(PkgConfig REQUIRED)
|
|
||||||
pkg_check_modules(GLIB2 REQUIRED glib-2.0)
|
|
||||||
pkg_check_modules(GOBJECT2 REQUIRED gobject-2.0)
|
|
||||||
pkg_check_modules(SOUP REQUIRED libsoup-2.4)
|
|
||||||
pkg_check_modules(JSON REQUIRED json-glib-1.0)
|
|
||||||
|
|
||||||
INCLUDE_DIRECTORIES(${GLIB2_INCLUDE_DIRS} ${GOBJECT2_INCLUDE_DIRS} ${SOUP_INCLUDE_DIRS} ${JSON_INCLUDE_DIRS})
|
|
||||||
LINK_DIRECTORIES(${GLIB2_LIBRARY_DIRS} ${GOBJECT2_LIBRARY_DIRS} ${SOUP_LIBRARY_DIRS} ${JSON_LIBRARY_DIRS})
|
|
||||||
add_definitions(${GLIB2_CFLAGS_OTHER} ${GOBJECT2_CFLAGS_OTHER} ${SOUP_CFLAGS_OTHER} ${JSON_CFLAGS_OTHER})
|
|
||||||
|
|
||||||
FILE(GLOB_RECURSE HEADERS "src/*.h")
|
|
||||||
aux_source_directory(./src SRC_LIST)
|
|
||||||
|
|
||||||
add_executable(${PROJECT_NAME} ${SRC_LIST} "${HEADERS}")
|
|
||||||
|
|
||||||
target_link_libraries(${PROJECT_NAME} ${SOUP_LIBRARIES} ${JSON_LIBRARIES} m)
|
|
||||||
|
|
||||||
install (TARGETS ${PROJECT_NAME} DESTINATION bin)
|
|
@ -1,2 +0,0 @@
|
|||||||
# lifo-dijkstraserv
|
|
||||||
A Dijkstra based route solver daemon for [lifomapserver](https://git.notsyncing.net:8080/markus/lifomapserver) GeoJson data.
|
|
54
htdocs/index.html
Normal file
54
htdocs/index.html
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>LinuxWorks Minetest Map</title>
|
||||||
|
<meta charset="utf-8"/>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<link rel="stylesheet" href="fa/css/all.css">
|
||||||
|
<link rel="stylesheet" href="awesomemarkers/leaflet.awesome-markers.css">
|
||||||
|
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.6.0/dist/leaflet.css" integrity="sha512-xwE/Az9zrjBIphAcBb3F6JVqxf46+CDLwfLMHloNu6KEQCAWi6HcDUbeOfBIptF7tcCzusKFjFw2yuvEpDL9wQ==" crossorigin=""/>
|
||||||
|
<script src="https://unpkg.com/leaflet@1.6.0/dist/leaflet.js" integrity="sha512-gZwIG9x3wUXg2hdXF6+rVkLF/0Vi9U8D2Ntg4Ga5I5BZpVkVxlJWbSQtXPSiUTtC0TjtGOmxa1AJPuV0CPthew==" crossorigin=""></script>
|
||||||
|
<script src="awesomemarkers/leaflet.awesome-markers.js"></script>
|
||||||
|
<!-- Streets -->
|
||||||
|
<script src='leafletjs/ctxtextpath.js'></script>
|
||||||
|
<script src='leafletjs/L.LabelTextCollision.js'></script>
|
||||||
|
<script src='leafletjs/Leaflet.streetlabels.js'></script>
|
||||||
|
<!-- Map Editor -->
|
||||||
|
<script src='leafletjs/Leaflet.Editable.js'></script>
|
||||||
|
</head>
|
||||||
|
<body style="position: absolute; padding: 0px; margin: 0px; width:100%; height:100%;">
|
||||||
|
<table id="windowtable" style="border-spacing:0;border:0;height:100%;width:100%;"><tr id="windowtr">
|
||||||
|
<td style="padding:0;margin:0;height:100%">
|
||||||
|
<div id="mapid" style="padding:0;margin:0;width: 100%; height: 100%;"></div>
|
||||||
|
</td>
|
||||||
|
</tr></table>
|
||||||
|
<style>
|
||||||
|
.leaflet-tooltip.city-names {
|
||||||
|
background-color: transparent;
|
||||||
|
border: transparent;
|
||||||
|
box-shadow: none;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 14pt;
|
||||||
|
opacity: 1.0;
|
||||||
|
color: white;
|
||||||
|
text-shadow: 0px 0px 5px black;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.new {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-aa img[role=presentation] {
|
||||||
|
image-rendering: optimizeSpeed; /* STOP SMOOTHING, GIVE ME SPEED */
|
||||||
|
image-rendering: -moz-crisp-edges; /* Firefox */
|
||||||
|
image-rendering: -o-crisp-edges; /* Opera */
|
||||||
|
image-rendering: -webkit-optimize-contrast; /* Chrome (and eventually Safari) */
|
||||||
|
image-rendering: pixelated; /* Chrome */
|
||||||
|
image-rendering: optimize-contrast; /* CSS3 Proposed */
|
||||||
|
-ms-interpolation-mode: nearest-neighbor; /* IE8+ */
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script src="mapscript.js"></script>
|
||||||
|
<script src="streeteditor.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
13
htdocs/leafletjs/L.LabelTextCollision.js
Normal file
13
htdocs/leafletjs/L.LabelTextCollision.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
L.LabelTextCollision=L.Canvas.extend({options:{collisionFlg:true},initialize:function(options){options=L.Util.setOptions(this,options);},_handleMouseHover:function(e,point){var id,layer;for(id in this._drawnLayers){layer=this._drawnLayers[id];if(layer.options.interactive&&layer._containsPoint(point)){L.DomUtil.addClass(this._containerText,'leaflet-interactive');this._fireEvent([layer],e,'mouseover');this._hoveredLayer=layer;}}
|
||||||
|
if(this._hoveredLayer){this._fireEvent([this._hoveredLayer],e);}},_handleMouseOut:function(e,point){var layer=this._hoveredLayer;if(layer&&(e.type==='mouseout'||!layer._containsPoint(point))){L.DomUtil.removeClass(this._containerText,'leaflet-interactive');this._fireEvent([layer],e,'mouseout');this._hoveredLayer=null;}},_updateTransform:function(center,zoom){L.Canvas.prototype._updateTransform.call(this,center,zoom);var scale=this._map.getZoomScale(zoom,this._zoom),position=L.DomUtil.getPosition(this._container),viewHalf=this._map.getSize().multiplyBy(0.5+this.options.padding),currentCenterPoint=this._map.project(this._center,zoom),destCenterPoint=this._map.project(center,zoom),centerOffset=destCenterPoint.subtract(currentCenterPoint),topLeftOffset=viewHalf.multiplyBy(-scale).add(position).add(viewHalf).subtract(centerOffset);if(L.Browser.any3d){L.DomUtil.setTransform(this._containerText,topLeftOffset,scale);}else{L.DomUtil.setPosition(this._containerText,topLeftOffset);}},_initContainer:function(options){L.Canvas.prototype._initContainer.call(this);this._containerText=document.createElement('canvas');L.DomEvent.on(this._containerText,'mousemove',L.Util.throttle(this._onMouseMove,32,this),this).on(this._containerText,'click dblclick mousedown mouseup contextmenu',this._onClick,this).on(this._containerText,'mouseout',this._handleMouseOut,this);this._ctxLabel=this._containerText.getContext('2d');L.DomUtil.addClass(this._containerText,'leaflet-zoom-animated');this.getPane().appendChild(this._containerText);},_update:function(){this._textList=[];L.Renderer.prototype._update.call(this);var b=this._bounds,container=this._containerText,size=b.getSize(),m=L.Browser.retina?2:1;L.DomUtil.setPosition(container,b.min);container.width=m*size.x;container.height=m*size.y;container.style.width=size.x+'px';container.style.height=size.y+'px';container.style.zIndex='4';this._container.style.zIndex='3';if(L.Browser.retina){this._ctxLabel.scale(2,2);}
|
||||||
|
this._ctxLabel.translate(-b.min.x,-b.min.y);L.Canvas.prototype._update.call(this);},_updatePoly:function(layer,closed){L.Canvas.prototype._updatePoly.call(this,layer,closed);this._text(this._ctxLabel,layer);},_updateCircle:function(layer){L.Canvas.prototype._updateCircle.call(this,layer);this._text(this._ctxLabel,layer);},_text:function(ctx,layer){if(layer.options.text!=undefined){ctx.globalAlpha=1;var p=layer._point;var textPoint;if(p==undefined){if(layer._parts.length==0||layer._parts[0].length==0){return;}
|
||||||
|
p=this._getCenter(layer._parts[0]);}
|
||||||
|
var offsetX=0;var offsetY=0;ctx.lineWidth=4.0;ctx.font="16px 'Meiryo'";var textWidth=(ctx.measureText(layer.options.text).width)
|
||||||
|
+p.x;var textHeight=p.y+offsetY+20;var bounds=L.bounds(L.point(p.x+offsetX,p.y+offsetY),L.point(textWidth,textHeight));if(this.options.collisionFlg){for(var index in this._textList){var pointBounds=this._textList[index];if(pointBounds.intersects(bounds)){return;}}}
|
||||||
|
this._textList.push(bounds);ctx.strokeStyle="white";ctx.strokeText(layer.options.text,p.x+offsetX,p.y
|
||||||
|
+offsetY);if(layer.options.textColor==undefined){ctx.fillStyle="blue";}else{ctx.fillStyle=layer.options.textColor;}
|
||||||
|
ctx.fillText(layer.options.text,p.x+offsetX,p.y
|
||||||
|
+offsetY);}},_getCenter:function(points){var i,halfDist,segDist,dist,p1,p2,ratio,len=points.length;if(!len){return null;}
|
||||||
|
for(i=0,halfDist=0;i<len-1;i++){halfDist+=points[i].distanceTo(points[i+1])/2;}
|
||||||
|
if(halfDist===0){return points[0];}
|
||||||
|
for(i=0,dist=0;i<len-1;i++){p1=points[i];p2=points[i+1];segDist=p1.distanceTo(p2);dist+=segDist;if(dist>halfDist){ratio=(dist-halfDist)/segDist;var resutl=[p2.x-ratio*(p2.x-p1.x),p2.y-ratio*(p2.y-p1.y)];return L.point(resutl[0],resutl[1]);}}},});
|
1946
htdocs/leafletjs/Leaflet.Editable.js
Normal file
1946
htdocs/leafletjs/Leaflet.Editable.js
Normal file
File diff suppressed because it is too large
Load Diff
197
htdocs/leafletjs/Leaflet.streetlabels.js
Normal file
197
htdocs/leafletjs/Leaflet.streetlabels.js
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
/*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
L.StreetLabels = L.LabelTextCollision
|
||||||
|
.extend({
|
||||||
|
|
||||||
|
options: {
|
||||||
|
/**
|
||||||
|
* Default property name to display the tooltip
|
||||||
|
*/
|
||||||
|
propertyName: 'name',
|
||||||
|
showLabelIf: null,
|
||||||
|
fontStyle: {
|
||||||
|
dynamicFontSize: false,
|
||||||
|
fontSize: 10,
|
||||||
|
fontSizeUnit: "px",
|
||||||
|
lineWidth: 4.0,
|
||||||
|
fillStyle: "black",
|
||||||
|
strokeStyle: "white",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
initialize: function (options) {
|
||||||
|
L.LabelTextCollision.prototype.initialize.call(this, options);
|
||||||
|
L.Util.stamp(this);
|
||||||
|
this._layers = this._layers || {};
|
||||||
|
},
|
||||||
|
|
||||||
|
_initContainer: function (options) {
|
||||||
|
L.LabelTextCollision.prototype._initContainer.call(this, options);
|
||||||
|
|
||||||
|
//Register the add/remove layers event to update the annotations accordingly
|
||||||
|
if (this._map) {
|
||||||
|
var handleLayerChanges = function () {
|
||||||
|
this._reset();
|
||||||
|
this._redraw();
|
||||||
|
}.bind(this);
|
||||||
|
this._map.on("layerremove", L.Util.throttle(handleLayerChanges, 32, this));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_text: function (ctx, layer) {
|
||||||
|
|
||||||
|
if (layer && layer.feature && layer.feature.properties && layer.feature.properties[this.options.propertyName] !== undefined) {
|
||||||
|
if (this.options.showLabelIf) {
|
||||||
|
if (this.options.showLabelIf.call(this, layer.feature) === false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var layerText = layer.feature.properties[this.options.propertyName];
|
||||||
|
ctx.globalAlpha = 1;
|
||||||
|
var p;
|
||||||
|
|
||||||
|
// polygon or polyline
|
||||||
|
if (layer._parts.length === 0 || layer._parts[0].length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (layer instanceof L.Polygon && this._map.hasLayer(layer)) {
|
||||||
|
p = this._getCentroid(layer);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
p = this._getCenter(layer._parts[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!p) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// label bounds offset
|
||||||
|
var offsetX = 0;
|
||||||
|
var offsetY = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO setting for custom font
|
||||||
|
*/
|
||||||
|
ctx.lineWidth = this.options.fontStyle.lineWidth;
|
||||||
|
|
||||||
|
var fontSize = this.options.fontStyle.fontSize;
|
||||||
|
if (this._map && this.options.fontStyle.dynamicFontSize === true) {
|
||||||
|
fontSize = this._getDynamicFontSize(layer.feature);
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.font = fontSize + this.options.fontStyle.fontSizeUnit + " 'Helvetica Neue',Helvetica,Arial,sans-serif";
|
||||||
|
|
||||||
|
// Collision detection
|
||||||
|
var textWidth = (ctx.measureText(layerText).width) + p.x;// + offsetX;
|
||||||
|
|
||||||
|
var textHeight = p.y + offsetY + 20;
|
||||||
|
|
||||||
|
var bounds = L.bounds(
|
||||||
|
L.point(p.x + offsetX, p.y + offsetY), L.point(
|
||||||
|
textWidth, textHeight));
|
||||||
|
|
||||||
|
if (this._getCollisionFlag(layer)) {
|
||||||
|
for (var index in this._textList) {
|
||||||
|
var pointBounds = this._textList[index];
|
||||||
|
if (pointBounds.intersects(bounds)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this._textList.push(bounds);
|
||||||
|
|
||||||
|
ctx.fillStyle = this.options.fontStyle.fillStyle;
|
||||||
|
ctx.strokeStyle = this.options.fontStyle.strokeStyle;
|
||||||
|
|
||||||
|
if (layer instanceof L.Polygon || layer instanceof L.CircleMarker) {
|
||||||
|
var textLength = ctx.measureText(layerText).width;
|
||||||
|
ctx.strokeText(layerText, p.x + offsetX - textLength / 2, p.y + offsetY);
|
||||||
|
ctx.fillText(layerText, p.x + offsetX - textLength / 2, p.y + offsetY);
|
||||||
|
} else if (layer instanceof L.Polyline) {
|
||||||
|
/**
|
||||||
|
* Render text alongside the polyline
|
||||||
|
* **/
|
||||||
|
var startCoords = layer.getLatLngs()[0];
|
||||||
|
var stopCoords = layer.getLatLngs()[layer.getLatLngs().length - 1];
|
||||||
|
|
||||||
|
//Flip lineString if bearing is negative
|
||||||
|
flip = (this._getBearing(startCoords, stopCoords) < 0);
|
||||||
|
|
||||||
|
if (layer._parts) {
|
||||||
|
ctx.textAlign = "center";
|
||||||
|
ctx.textBaseline = "middle";
|
||||||
|
//ctx.lineWidth = 3;
|
||||||
|
layer._parts.forEach(function (part) {
|
||||||
|
//Build the points list for the first part
|
||||||
|
var pathPoints = [];
|
||||||
|
for (var i = 0; i < part.length; i++) {
|
||||||
|
var linePart = (flip ? part[part.length - 1 - i] : part[i]);
|
||||||
|
pathPoints.push(linePart.x);
|
||||||
|
pathPoints.push(linePart.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.textPath(layerText, pathPoints);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_getCollisionFlag: function (layer) {
|
||||||
|
return this.options.collisionFlg;
|
||||||
|
},
|
||||||
|
|
||||||
|
/***
|
||||||
|
* Returns the bearing in degrees clockwise from north (0 degrees)
|
||||||
|
from the first L.LatLng to the second, at the first LatLng
|
||||||
|
@param {L.LatLng} latlng1: origin point of the bearing
|
||||||
|
@param {L.LatLng} latlng2: destination point of the bearing
|
||||||
|
@returns {float} degrees clockwise from north.
|
||||||
|
Source: https://makinacorpus.github.io/Leaflet.GeometryUtil/leaflet.geometryutil.js.html
|
||||||
|
*/
|
||||||
|
_getBearing: function (startCoords, stopCoords) {
|
||||||
|
if (startCoords.lat == stopCoords.lat) {
|
||||||
|
if (startCoords.lng > stopCoords.lng)
|
||||||
|
return -180;
|
||||||
|
else
|
||||||
|
return 180;
|
||||||
|
}
|
||||||
|
if (startCoords.lng == stopCoords.lng) {
|
||||||
|
if (startCoords.lat > stopCoords.lat)
|
||||||
|
return -90;
|
||||||
|
else
|
||||||
|
return 90;
|
||||||
|
}
|
||||||
|
var angle = Math.atan(stopCoords.lng - startCoords.lng, stopCoords.lat - startCoords.lat) * 180 / Math.Pi;
|
||||||
|
return angle;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
Returns a clone with reversed coordinates.
|
||||||
|
@param {L.PolyLine} polyline polyline to reverse
|
||||||
|
@returns {L.PolyLine} polyline reversed
|
||||||
|
Source: https://makinacorpus.github.io/Leaflet.GeometryUtil/leaflet.geometryutil.js.html
|
||||||
|
*/
|
||||||
|
_getLineStringReverse: function (polyline) {
|
||||||
|
var latLngs = polyline.getLatLngs().slice(0).reverse();
|
||||||
|
polyline.setLatLngs(latLngs);
|
||||||
|
return polyline;
|
||||||
|
},
|
||||||
|
|
||||||
|
_getDynamicFontSize: function (layer) {
|
||||||
|
return parseInt(this._map.getZoom());
|
||||||
|
},
|
||||||
|
|
||||||
|
_getCentroid: function (layer) {
|
||||||
|
if (layer && layer.getCenter && this._map) {
|
||||||
|
var latlngCenter = layer.getCenter();
|
||||||
|
var containerPoint = this._map.latLngToContainerPoint(latlngCenter);
|
||||||
|
return this._map.containerPointToLayerPoint(containerPoint);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
35
htdocs/leafletjs/ctxtextpath.js
Normal file
35
htdocs/leafletjs/ctxtextpath.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
(function()
|
||||||
|
{function dist2D(x1,y1,x2,y2)
|
||||||
|
{var dx=x2-x1;var dy=y2-y1;return Math.sqrt(dx*dx+dy*dy);}
|
||||||
|
CanvasRenderingContext2D.prototype.textOverflow="";CanvasRenderingContext2D.prototype.textJustify=false;CanvasRenderingContext2D.prototype.textStrokeMin=0;var state=[];var save=CanvasRenderingContext2D.prototype.save;CanvasRenderingContext2D.prototype.save=function()
|
||||||
|
{state.push({textOverflow:this.textOverflow,textJustify:this.textJustify,textStrokeMin:this.textStrokeMin,});save.call(this);}
|
||||||
|
var restore=CanvasRenderingContext2D.prototype.restore;CanvasRenderingContext2D.prototype.restore=function()
|
||||||
|
{restore.call(this);var s=state.pop();this.textOverflow=s.textOverflow;this.textJustify=s.textJustify;this.textStrokeMin=s.textStrokeMin;}
|
||||||
|
CanvasRenderingContext2D.prototype.textPath=function(text,path)
|
||||||
|
{var di,dpos=0;var pos=2;function pointAt(dl)
|
||||||
|
{if(!di||dpos+di<dl)
|
||||||
|
{for(;pos<path.length;)
|
||||||
|
{di=dist2D(path[pos-2],path[pos-1],path[pos],path[pos+1]);if(dpos+di>dl)break;pos+=2;if(pos>=path.length)break;dpos+=di;}}
|
||||||
|
var x,y,dt=dl-dpos;if(pos>=path.length)
|
||||||
|
{pos=path.length-2;}
|
||||||
|
if(!dt)
|
||||||
|
{x=path[pos-2];y=path[pos-1];}
|
||||||
|
else
|
||||||
|
{x=path[pos-2]+(path[pos]-path[pos-2])*dt/di;y=path[pos-1]+(path[pos+1]-path[pos-1])*dt/di;}
|
||||||
|
return[x,y,Math.atan2(path[pos+1]-path[pos-1],path[pos]-path[pos-2])];}
|
||||||
|
var letterPadding=this.measureText(" ").width*0.25;var d=0;for(var i=2;i<path.length;i+=2)
|
||||||
|
{d+=dist2D(path[i-2],path[i-1],path[i],path[i+1])}
|
||||||
|
if(d<this.minWidth)return;var nbspace=text.split(" ").length-1;if(this.textOverflow!="visible")
|
||||||
|
{if(d<this.measureText(text).width+(text.length-1+nbspace)*letterPadding)
|
||||||
|
{var overflow=(this.textOverflow=="ellipsis")?'\u2026':this.textOverflow||"";var dt=overflow.length-1;do
|
||||||
|
{if(text[text.length-1]===" ")nbspace--;text=text.slice(0,-1);}while(text&&d<this.measureText(text+overflow).width+(text.length+dt+nbspace)*letterPadding)
|
||||||
|
text+=overflow;}}
|
||||||
|
var start=0;switch(this.textJustify||this.textAlign)
|
||||||
|
{case true:case "center":case "end":case "right":{if(this.textJustify)
|
||||||
|
{start=0;letterPadding=(d-this.measureText(text).width)/(text.length-1+nbspace);}
|
||||||
|
else
|
||||||
|
{start=d-this.measureText(text).width-(text.length+nbspace)*letterPadding;if(this.textAlign=="center")start/=2;}
|
||||||
|
break;}
|
||||||
|
default:break;}
|
||||||
|
for(var t=0;t<text.length;t++)
|
||||||
|
{var letter=text[t];var wl=this.measureText(letter).width;var p=pointAt(start+wl/2);this.save();this.textAlign="center";this.translate(p[0],p[1]);this.rotate(p[2]);if(this.lineWidth>0.1)this.strokeText(letter,0,0);this.fillText(letter,0,0);this.restore();start+=wl+letterPadding*(letter==" "?2:1);}};})();
|
759
htdocs/mapscript.js
Normal file
759
htdocs/mapscript.js
Normal file
@ -0,0 +1,759 @@
|
|||||||
|
var editor_mode = 0;
|
||||||
|
var mymap;
|
||||||
|
|
||||||
|
var polyconf_show_street_names = 5; // Zoom level for when to start showing street names
|
||||||
|
var polyconf_show_cities = 5; /* City outlines will be filled on this level and further away */
|
||||||
|
var polyconf_show_districts = 4; /* Shown from polyconf_show_cities until this*/
|
||||||
|
|
||||||
|
var dijkstraserv_base = "https://notsyncing.net/dijkstra/";
|
||||||
|
var wikiurl_base = "https://wiki.linux-forks.de/mediawiki/index.php/"
|
||||||
|
var tiles_base = "https://notsyncing.net/maps.linux-forks.de/tiles/"
|
||||||
|
|
||||||
|
var streetLabelsRenderer = new L.StreetLabels({
|
||||||
|
collisionFlg: true,
|
||||||
|
propertyName: 'name',
|
||||||
|
showLabelIf: function (layer) {
|
||||||
|
switch (layer.geometry.type) {
|
||||||
|
case "LineString":
|
||||||
|
if (mymap.getZoom() <= polyconf_show_street_names)
|
||||||
|
return false;
|
||||||
|
break;
|
||||||
|
case "Polygon":
|
||||||
|
if (mymap.getZoom() > polyconf_show_street_names)
|
||||||
|
return false;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
fontStyle: {
|
||||||
|
dynamicFontSize: true,
|
||||||
|
fontSize: 10,
|
||||||
|
fontSizeUnit: "px",
|
||||||
|
lineWidth: 3.0,
|
||||||
|
fillStyle: "black",
|
||||||
|
strokeStyle: "white",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
streetLabelsRenderer._getCollisionFlag = function (layer) {
|
||||||
|
if (!(layer instanceof L.Polygon)) // Always check collision for streets
|
||||||
|
return true;
|
||||||
|
zoom = mymap.getZoom();
|
||||||
|
return (zoom < 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
streetLabelsRenderer._getDynamicFontSize = function (layer) {
|
||||||
|
zoom = mymap.getZoom();
|
||||||
|
|
||||||
|
switch (layer.geometry.type) {
|
||||||
|
case "LineString":
|
||||||
|
if (zoom <= 8)
|
||||||
|
return 11;
|
||||||
|
else
|
||||||
|
return 2**(zoom - 9) * 11;
|
||||||
|
break;
|
||||||
|
case "Polygon":
|
||||||
|
var size;
|
||||||
|
if (zoom >= 4)
|
||||||
|
size = 30;
|
||||||
|
else
|
||||||
|
size = 20;
|
||||||
|
switch (layer.properties.type) {
|
||||||
|
case "city":
|
||||||
|
break;
|
||||||
|
case "town":
|
||||||
|
size *= 2.0/3.0;
|
||||||
|
break;
|
||||||
|
case "village":
|
||||||
|
size *= 1.5/3;
|
||||||
|
break;
|
||||||
|
case "district":
|
||||||
|
size *= 2.0/3;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
size *= 0.5/3;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return size;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
var style_outlines = {
|
||||||
|
radius: 8,
|
||||||
|
fillColor: "#ff7800",
|
||||||
|
color: "black",
|
||||||
|
opacity: 1.0,
|
||||||
|
fillOpacity: 0.5
|
||||||
|
};
|
||||||
|
|
||||||
|
var style_streets = {
|
||||||
|
};
|
||||||
|
|
||||||
|
var style_route = {
|
||||||
|
radius: 8,
|
||||||
|
fillColor: "#00ff00",
|
||||||
|
color: "red",
|
||||||
|
opacity: 1.0,
|
||||||
|
fillOpacity: 0.5
|
||||||
|
};
|
||||||
|
|
||||||
|
// Projection fix from: https://gis.stackexchange.com/questions/200865/leaflet-crs-simple-custom-scale
|
||||||
|
var factorx = 1 / 256 * 4;
|
||||||
|
var factory = factorx;
|
||||||
|
var originx = 7000 + 8 + 0.5;
|
||||||
|
var originy = 7000 + 8 - 0.5;
|
||||||
|
var zoom_level_real = 6;
|
||||||
|
|
||||||
|
L.CRS.pr = L.extend({}, L.CRS.Simple, {
|
||||||
|
projection: L.Projection.LonLat,
|
||||||
|
transformation: new L.Transformation(factorx,factorx * originx,-factory,factory * originy),
|
||||||
|
|
||||||
|
scale: function(zoom) {
|
||||||
|
return Math.pow(2, zoom);
|
||||||
|
},
|
||||||
|
|
||||||
|
zoom: function(scale) {
|
||||||
|
return Math.log(scale) / Math.LN2;
|
||||||
|
},
|
||||||
|
|
||||||
|
distance: function(latlng1, latlng2) {
|
||||||
|
var dx = latlng2.lng - latlng1.lng
|
||||||
|
, dy = latlng2.lat - latlng1.lat;
|
||||||
|
|
||||||
|
return Math.sqrt(dx * dx + dy * dy);
|
||||||
|
},
|
||||||
|
infinite: true
|
||||||
|
});
|
||||||
|
|
||||||
|
// Init map
|
||||||
|
mymap = L.map('mapid', {
|
||||||
|
renderer: streetLabelsRenderer,
|
||||||
|
editable: true,
|
||||||
|
crs: L.CRS.pr
|
||||||
|
}).setView([0, 0], 5);
|
||||||
|
|
||||||
|
var mapheight = 16384;
|
||||||
|
var mapwidth = mapheight;
|
||||||
|
var sw = mymap.unproject([0, 0], zoom_level_real);
|
||||||
|
var ne = mymap.unproject([mapwidth, mapheight], zoom_level_real);
|
||||||
|
var layerbounds = new L.LatLngBounds(sw,ne);
|
||||||
|
|
||||||
|
var layers = L.control.layers({}, {}).addTo(mymap);
|
||||||
|
|
||||||
|
function load_svg(name, url, active=1) {
|
||||||
|
var xhttp_ps = new XMLHttpRequest();
|
||||||
|
xhttp_ps.onreadystatechange = function() {
|
||||||
|
if (this.readyState == 4) {
|
||||||
|
if (this.status == 200) {
|
||||||
|
var svgElement = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
||||||
|
svgElement.setAttribute('xmlns', "http://www.w3.org/2000/svg");
|
||||||
|
svgElement.setAttribute('viewBox', "0 0 16384 16384");
|
||||||
|
svgElement.innerHTML = xhttp_ps.responseText;
|
||||||
|
var svgElementBounds = [[0, 0], [1000, 1000]];
|
||||||
|
var overlay = L.svgOverlay(svgElement, svgElementBounds);
|
||||||
|
layers.addOverlay(overlay, name);
|
||||||
|
if (active)
|
||||||
|
overlay.addTo(mymap);
|
||||||
|
return overlay;
|
||||||
|
} else {
|
||||||
|
alert("Error: Could not load SVG map layer (" + name + ")");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
;
|
||||||
|
xhttp_ps.open("GET", url, true);
|
||||||
|
xhttp_ps.send();
|
||||||
|
}
|
||||||
|
|
||||||
|
function load_tiles(name, id) {
|
||||||
|
var url = "";
|
||||||
|
if (name != "") {
|
||||||
|
url = tiles_base + '{id}/{z}/{y}/{x}.png';
|
||||||
|
}
|
||||||
|
var satellite = L.tileLayer(url, {
|
||||||
|
maxZoom: 14 /*8*/,
|
||||||
|
maxNativeZoom: 6,
|
||||||
|
minNativeZoom: 0,
|
||||||
|
minZoom: 0,
|
||||||
|
noWrap: true,
|
||||||
|
attribution: 'Map data © <a href="' + wikiurl_base + 'Maps">Linux-Forks</a>',
|
||||||
|
id: id,
|
||||||
|
tileSize: 256,
|
||||||
|
zoomOffset: 0,
|
||||||
|
opacity: 1.0,
|
||||||
|
bounds: layerbounds
|
||||||
|
});
|
||||||
|
layers.addBaseLayer(satellite, name);
|
||||||
|
return satellite;
|
||||||
|
}
|
||||||
|
|
||||||
|
var current_location = "";
|
||||||
|
var current_feature = null;
|
||||||
|
function load_geojson(name, url, geotype, iconcolor, active=1, style={}) {
|
||||||
|
var xhttp_ps = new XMLHttpRequest();
|
||||||
|
xhttp_ps.onreadystatechange = function() {
|
||||||
|
if (this.readyState == 4) {
|
||||||
|
if (this.status == 200) {
|
||||||
|
onEachFeature = null;
|
||||||
|
pointToLayer = null;
|
||||||
|
filter = null;
|
||||||
|
|
||||||
|
switch (geotype) {
|
||||||
|
case "street":
|
||||||
|
onEachFeature = function(feature, layer) {
|
||||||
|
layer.myTag = geotype;
|
||||||
|
layer.myName = name;
|
||||||
|
layer.on("click", function (e) {
|
||||||
|
current_feature = feature;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case "outline":
|
||||||
|
onEachFeature = function(feature, layer) {
|
||||||
|
layer.myTag = geotype;
|
||||||
|
layer.myName = name;
|
||||||
|
layer.on("click", function (e) {
|
||||||
|
current_location = feature.properties.name;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default: /* else it is a marker with the specified icon */
|
||||||
|
onEachFeature = function(feature, layer) {
|
||||||
|
label = String(feature.properties.name)
|
||||||
|
layer.bindPopup('<h1><a target="_blank" href="' + wikiurl_base + feature.properties.name + '">' + feature.properties.name + '</a> (' + feature.geometry.coordinates + ')</h1>' + '<p><img style="width:100%" src="' + feature.properties.image + '"></p>' + '<p>' + feature.properties.description + '</p>');
|
||||||
|
layer.bindTooltip(label, {
|
||||||
|
permanent: true,
|
||||||
|
direction: "center",
|
||||||
|
className: "city-names"
|
||||||
|
}).openTooltip().on('click', jump_to_marker);
|
||||||
|
};
|
||||||
|
pointToLayer = function(feature, latlng) {
|
||||||
|
label = String(feature.properties.name)
|
||||||
|
if (geotype == "auto") {
|
||||||
|
iconname = "star"; // default icon
|
||||||
|
iconcolor = "orange";
|
||||||
|
for (var i = 0; i < feature.properties.categories.length; i++) {
|
||||||
|
category = feature.properties.categories[i].toLowerCase();
|
||||||
|
switch (category) {
|
||||||
|
case "stations":
|
||||||
|
iconname = "train";
|
||||||
|
iconcolor = "blue";
|
||||||
|
break;
|
||||||
|
case "shops":
|
||||||
|
iconname = "shopping-cart";
|
||||||
|
iconcolor = "green";
|
||||||
|
break;
|
||||||
|
case "city":
|
||||||
|
case "village":
|
||||||
|
case "town":
|
||||||
|
case "settlements":
|
||||||
|
iconname = "city";
|
||||||
|
iconcolor = "red";
|
||||||
|
break;
|
||||||
|
case "parks":
|
||||||
|
iconname = "tree";
|
||||||
|
iconcolor = "green";
|
||||||
|
break;
|
||||||
|
case "courts":
|
||||||
|
iconname = "balance-scale";
|
||||||
|
iconcolor = "black";
|
||||||
|
break;
|
||||||
|
case "train depots":
|
||||||
|
iconname = "wrench";
|
||||||
|
iconcolor = "violet";
|
||||||
|
break;
|
||||||
|
case "hotels":
|
||||||
|
iconname = "hotel";
|
||||||
|
iconcolor = "gray";
|
||||||
|
break;
|
||||||
|
case "beaches":
|
||||||
|
iconname = "umbrella-beach";
|
||||||
|
iconcolor = "orange";
|
||||||
|
break;
|
||||||
|
case "waterway":
|
||||||
|
iconname = "water";
|
||||||
|
iconcolor = "blue";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (iconname != "star")
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
iconname = geotype;
|
||||||
|
}
|
||||||
|
marker = new L.marker(latlng,{
|
||||||
|
icon: L.AwesomeMarkers.icon({
|
||||||
|
icon: iconname,
|
||||||
|
markerColor: iconcolor
|
||||||
|
})
|
||||||
|
}).bindTooltip(label, {
|
||||||
|
permanent: false,
|
||||||
|
direction: "center",
|
||||||
|
opacity: 0.7
|
||||||
|
}).openTooltip();
|
||||||
|
return marker;
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
var json = JSON.parse(xhttp_ps.responseText);
|
||||||
|
geojson = L.geoJSON(json, {
|
||||||
|
style: style,
|
||||||
|
onEachFeature: onEachFeature,
|
||||||
|
pointToLayer: pointToLayer,
|
||||||
|
filter: filter
|
||||||
|
});
|
||||||
|
layers.addOverlay(geojson, name);
|
||||||
|
|
||||||
|
if (active)
|
||||||
|
geojson.addTo(mymap);
|
||||||
|
return geojson;
|
||||||
|
} else {
|
||||||
|
console.log("Error: Could not load geojson map layer (" + name + ").");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
;
|
||||||
|
xhttp_ps.open("GET", url, true);
|
||||||
|
xhttp_ps.send();
|
||||||
|
}
|
||||||
|
|
||||||
|
load_tiles("Satellite (2020-04-09)", 'world-2020-04-09').addTo(mymap);
|
||||||
|
load_tiles("Satellite (2019-05-04, wrong coords)", 'world-2019-05-04');
|
||||||
|
load_tiles("None", '');
|
||||||
|
|
||||||
|
load_geojson("All", "./geojson/all.json", "auto", "auto", 0);
|
||||||
|
load_geojson("Streets", "./geojson/streets.json", "street", "auto", 1, style_streets);
|
||||||
|
load_geojson("Cities", "./geojson/city_outlines.json", "outline", "auto", 1, style_outlines);
|
||||||
|
|
||||||
|
L.control.scale().addTo(mymap);
|
||||||
|
|
||||||
|
function resolve_latlng(latlng, recenter = 0) {
|
||||||
|
latlng.lng = Math.round(latlng.lng);
|
||||||
|
latlng.lat = Math.round(latlng.lat);
|
||||||
|
return latlng;
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_current_location_str() {
|
||||||
|
var latlng = resolve_latlng(mymap.getCenter());
|
||||||
|
return latlng.lng + "," + latlng.lat + "," + mymap.getZoom();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Important: Do not use mymap.setView, use this function instead */
|
||||||
|
function jump_to(latlng, zoom = -1) {
|
||||||
|
if (zoom == -1)
|
||||||
|
zoom = mymap.getZoom();
|
||||||
|
if (!editor_mode) {
|
||||||
|
pos = resolve_latlng(latlng);
|
||||||
|
document.location.hash = "#" + pos.lng + "," + pos.lat + "," + zoom;
|
||||||
|
} else {
|
||||||
|
mymap.setView(latlng, zoom);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function jump_to_marker(e) {
|
||||||
|
jump_to(e.target.getLatLng());
|
||||||
|
}
|
||||||
|
|
||||||
|
function prompt_location() {
|
||||||
|
var str = prompt("Enter coordinates to jump to:\n\n" +
|
||||||
|
"Format: x, y [, zoom]", get_current_location_str());
|
||||||
|
if (str) {
|
||||||
|
if (!editor_mode)
|
||||||
|
document.location.hash = "#" + str;
|
||||||
|
else
|
||||||
|
onHashChange(null, "#" + str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var search_element;
|
||||||
|
var route_element;
|
||||||
|
function build_sidebar() {
|
||||||
|
if (!search_element) {
|
||||||
|
search_element = document.createElement("div");
|
||||||
|
search_element.style.overflow = "scroll";
|
||||||
|
search_element.style.padding = "6px";
|
||||||
|
search_element.style.height = "100%";
|
||||||
|
search_element.innerHTML = '<input style="width:100%;" id="search_query" name="lifo_map_search" type="search" placeholder="Start typing to search..." oninput="search(event)"><div id="search_results"></div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!route_element) {
|
||||||
|
route_element = document.createElement("div");
|
||||||
|
route_element.style.overflow = "scroll";
|
||||||
|
route_element.style.padding = "6px";
|
||||||
|
route_element.style.height = "100%";
|
||||||
|
route_element.innerHTML = '<a href="#" onclick="toggle_search(\'search\',1);return false;"><-- Return to search<br></a><input style="width:100%;" id="route_start" name="lifo_route_loc" type="search" placeholder="Start at x,y"><br><input style="width:100%;" id="route_destination" name="lifo_route_loc" type="search" placeholder="Go to x,y"><br><input id="route_submit" type="button" style="width:100%;" value="Go!" onclick="route(event)"><div id="route_results"></div>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var current_sidebar = "search";
|
||||||
|
function toggle_search(type = current_sidebar, force_act = 0) {
|
||||||
|
if (el = document.getElementById('sidebar')) {
|
||||||
|
el.parentNode.removeChild(el);
|
||||||
|
if (force_act == 0 && type == current_sidebar)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
td = document.getElementById('windowtr').insertCell(0);
|
||||||
|
td.style.width = "400px";
|
||||||
|
td.style.borderRight = "1px solid black";
|
||||||
|
td.style.verticalAlign = "top";
|
||||||
|
td.id = "sidebar";
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case "search":
|
||||||
|
td.appendChild(search_element);
|
||||||
|
document.getElementById('search_query').select();
|
||||||
|
break;
|
||||||
|
case "route":
|
||||||
|
td.appendChild(route_element);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
alert("JS error: sidebar");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
current_sidebar = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
function htmlEntities(str) {
|
||||||
|
return String(str).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
||||||
|
}
|
||||||
|
|
||||||
|
function polyline_get_middle_coords(coords) {
|
||||||
|
var ret = [2];
|
||||||
|
ret[0] = coords[0][0] + (coords[coords.length - 1][0] - coords[0][0]) / 2;
|
||||||
|
ret[1] = coords[0][1] + (coords[coords.length - 1][1] - coords[0][1]) / 2;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
var highlighted_line;
|
||||||
|
var default_street_color = "#3388ff";
|
||||||
|
|
||||||
|
function search(e) {
|
||||||
|
var query = document.getElementById("search_query").value;
|
||||||
|
var real_query = true;
|
||||||
|
document.getElementById('search_results').innerHTML = "";
|
||||||
|
if (true) {
|
||||||
|
if (query.length == 0) {
|
||||||
|
real_query = false;
|
||||||
|
query = "!@#$%^&"; // Cheap workaround to (hopefully) match nothing
|
||||||
|
}
|
||||||
|
results = document.createElement("ul");
|
||||||
|
for (var i = 0; i < layers._layers.length; i++) {
|
||||||
|
if (!layers._layers[i].layer._layers)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
for (key in layers._layers[i].layer._layers) {
|
||||||
|
item = layers._layers[i].layer._layers[key];
|
||||||
|
switch (item.feature.geometry.type) {
|
||||||
|
case "Point":
|
||||||
|
regex = new RegExp(query, 'i');
|
||||||
|
if (item.feature.properties.name.match(regex)) {
|
||||||
|
el = document.createElement("li");
|
||||||
|
el.innerHTML = "[" + layers._layers[i].name + "] " + '<a href="#" onclick="layers._layers[' + i + '].layer._layers[' + item._leaflet_id + '].fire(\'click\'); return false;">' + htmlEntities(item.feature.properties.name) + "</a>";
|
||||||
|
results.appendChild(el);
|
||||||
|
item.setOpacity(1.0); // TODO: setOpacity does not disable hover and click events
|
||||||
|
} else {
|
||||||
|
if (real_query)
|
||||||
|
item.setOpacity(0.0); // TODO
|
||||||
|
else
|
||||||
|
item.setOpacity(1.0); // TODO
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "LineString":
|
||||||
|
if (item.options.color != default_street_color) { // De-hilight last search
|
||||||
|
item.options.color = default_street_color;
|
||||||
|
item.redraw();
|
||||||
|
}
|
||||||
|
regex = new RegExp(query, 'i');
|
||||||
|
|
||||||
|
if (item.feature.properties.name.match(regex)) {
|
||||||
|
item.options.color = "#FF0000";
|
||||||
|
item.redraw();
|
||||||
|
el = document.createElement("li");
|
||||||
|
zpos = polyline_get_middle_coords(item.feature.geometry.coordinates);
|
||||||
|
el.innerHTML = "[" + layers._layers[i].name + "] " + '<a href="#" onclick="latLng2 = L.latLng(' + zpos[1] + ',' + zpos[0] + '); jump_to(latLng2); return false;">' + htmlEntities(item.feature.properties.name) + "</a>";
|
||||||
|
results.appendChild(el);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "Polygon":
|
||||||
|
regex = new RegExp(query, 'i');
|
||||||
|
|
||||||
|
if (item.feature.properties.name.match(regex)) {
|
||||||
|
el = document.createElement("li");
|
||||||
|
zpos = resolve_latlng(layers._layers[i].layer._layers[item._leaflet_id].getCenter());
|
||||||
|
el.innerHTML = "[" + layers._layers[i].name + "] " + '<a href="#" onclick="latLng2 = L.latLng(' + zpos.lat + ',' + zpos.lng + '); jump_to(latLng2, ' + polyconf_show_cities + '); return false;">' + htmlEntities(item.feature.properties.name) + "</a>";
|
||||||
|
results.appendChild(el);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
document.getElementById('search_results').appendChild(results);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var route_layer;
|
||||||
|
function route(e) // BOOKMARK
|
||||||
|
{
|
||||||
|
if (route_layer) {
|
||||||
|
mymap.removeLayer(route_layer);
|
||||||
|
}
|
||||||
|
|
||||||
|
start = document.getElementById('route_start').value;
|
||||||
|
destination = document.getElementById('route_destination').value;
|
||||||
|
if ((start == "") || (destination == "")) {
|
||||||
|
document.getElementById('route_results').innerHTML = "";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('route_submit').disabled = true;
|
||||||
|
document.getElementById('route_results').innerHTML = "Loading...";
|
||||||
|
|
||||||
|
var xhttp_ps = new XMLHttpRequest();
|
||||||
|
xhttp_ps.onreadystatechange = function() {
|
||||||
|
if (this.readyState == 4) {
|
||||||
|
if (this.status == 200) {
|
||||||
|
var json = JSON.parse(xhttp_ps.responseText);
|
||||||
|
geojson = L.geoJSON(json, {
|
||||||
|
style: style_route
|
||||||
|
});
|
||||||
|
route_layer = geojson.addTo(mymap);
|
||||||
|
|
||||||
|
str = "<ol>";
|
||||||
|
last_through = "";
|
||||||
|
geojson.eachLayer( function(layer) {
|
||||||
|
if (last_through != layer.feature.properties.through) {
|
||||||
|
str += '<li><a href="#" onclick="latLng2 = L.latLng(' + layer.feature.geometry.coordinates[0][1] + ',' + layer.feature.geometry.coordinates[0][0] + '); jump_to(latLng2); return false;">' + htmlEntities(layer.feature.properties.description) + "</a></li>";
|
||||||
|
last_through = layer.feature.properties.through;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
str += "</ol>";
|
||||||
|
document.getElementById('route_results').innerHTML = str;
|
||||||
|
} else {
|
||||||
|
document.getElementById('route_results').innerHTML = "No results.";
|
||||||
|
}
|
||||||
|
document.getElementById('route_submit').disabled = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
coordstr = start + ';' + destination;
|
||||||
|
xhttp_ps.open("GET", dijkstraserv_base + coordstr, true);
|
||||||
|
xhttp_ps.send();
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_route_start(latlng) {
|
||||||
|
toggle_search("route", 1);
|
||||||
|
document.getElementById('route_start').value = latlng.lat + ',' + latlng.lng;
|
||||||
|
route();
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_route_destination(latlng) {
|
||||||
|
toggle_search("route", 1);
|
||||||
|
document.getElementById('route_destination').value = latlng.lat + ',' + latlng.lng;
|
||||||
|
route();
|
||||||
|
}
|
||||||
|
|
||||||
|
L.MyControl = L.Control.extend({
|
||||||
|
options: {
|
||||||
|
position: 'topleft',
|
||||||
|
callback: null,
|
||||||
|
kind: '',
|
||||||
|
html: ''
|
||||||
|
},
|
||||||
|
onAdd: function (map) {
|
||||||
|
var container = L.DomUtil.create('div', 'leaflet-control leaflet-bar'),
|
||||||
|
link = L.DomUtil.create('a', '', container);
|
||||||
|
|
||||||
|
link.href = '#';
|
||||||
|
link.title = this.options.title;
|
||||||
|
link.innerHTML = this.options.html;
|
||||||
|
L.DomEvent.on(link, 'mousedown', L.DomEvent.stop);
|
||||||
|
L.DomEvent.on(link, 'mouseup', L.DomEvent.stop);
|
||||||
|
L.DomEvent.on(link, 'click', L.DomEvent.stop)
|
||||||
|
.on(link, 'click', function () {
|
||||||
|
window.LAYER = this.options.callback.call();
|
||||||
|
}, this);
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
L.SearchControl = L.MyControl.extend({
|
||||||
|
options: {
|
||||||
|
position: 'topleft',
|
||||||
|
callback: toggle_search,
|
||||||
|
title: 'Search',
|
||||||
|
html: '🔍'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
L.GotoControl = L.MyControl.extend({
|
||||||
|
options: {
|
||||||
|
position: 'topleft',
|
||||||
|
callback: prompt_location,
|
||||||
|
title: 'Jump to coordinates',
|
||||||
|
html: '⌖'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
mymap.addControl(new L.SearchControl());
|
||||||
|
mymap.addControl(new L.GotoControl());
|
||||||
|
|
||||||
|
L.CurrentPosControl = L.Control.extend({
|
||||||
|
options: {
|
||||||
|
position : 'bottomleft',
|
||||||
|
callback: null,
|
||||||
|
kind: "",
|
||||||
|
html: ''
|
||||||
|
},
|
||||||
|
onAdd: function (map) {
|
||||||
|
container = L.DomUtil.create('div', 'leaflet-control-scale leaflet-bar');
|
||||||
|
container.style.visibility = "hidden";
|
||||||
|
div = L.DomUtil.create('div', '', container);
|
||||||
|
div.style.backgroundColor = "white";
|
||||||
|
div.style.paddingLeft = "4px";
|
||||||
|
div.style.paddingRight = "4px";
|
||||||
|
div.style.borderRadius = "2px";
|
||||||
|
div.style.margin = "2px";
|
||||||
|
div.style.opacity = "0.6";
|
||||||
|
div.style.fontWeight = "bold";
|
||||||
|
div.innerHTML = "";
|
||||||
|
div.id = "current_position_label";
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
mymap.addControl(new L.CurrentPosControl());
|
||||||
|
|
||||||
|
var popup = L.popup();
|
||||||
|
|
||||||
|
L.AwesomeMarkers.Icon.prototype.options.prefix = 'fa';
|
||||||
|
var baseballIcon = L.AwesomeMarkers.icon({
|
||||||
|
icon: 'coffee',
|
||||||
|
markerColor: 'red'
|
||||||
|
});
|
||||||
|
|
||||||
|
function onMapClick(e) {
|
||||||
|
var addinfo = "";
|
||||||
|
pos = resolve_latlng(e.latlng);
|
||||||
|
route_links = '<br><a href="#" onclick="latLng2 = L.latLng(' + pos.lng + ',' + pos.lat + '); set_route_start(latLng2); return false;">[route from here]</a>';
|
||||||
|
route_links += ' <a href="#" onclick="latLng2 = L.latLng(' + pos.lng + ',' + pos.lat + '); set_route_destination(latLng2); return false;">[route to here]</a>';
|
||||||
|
if (current_location != "")
|
||||||
|
addinfo = " (part of <a target=\"_blank\" href='" + wikiurl_base + current_location + "'>" + current_location + "</a>)";
|
||||||
|
if (current_feature) {
|
||||||
|
popup.setLatLng(e.latlng).setContent("This is " + current_feature.properties.name + addinfo + route_links).openOn(mymap);
|
||||||
|
} else {
|
||||||
|
popup.setLatLng(e.latlng).setContent("You clicked the map at " + pos.lng + "," + pos.lat + addinfo + route_links).openOn(mymap);
|
||||||
|
}
|
||||||
|
current_feature = null;
|
||||||
|
current_location = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
mymap.on('click', onMapClick);
|
||||||
|
|
||||||
|
var is_user_drag = 0;
|
||||||
|
|
||||||
|
function update_hash_from_position(e) {
|
||||||
|
if (is_user_drag)
|
||||||
|
is_user_drag = 0;
|
||||||
|
else
|
||||||
|
return;
|
||||||
|
if (!editor_mode)
|
||||||
|
document.location.hash = "#" + get_current_location_str();
|
||||||
|
}
|
||||||
|
|
||||||
|
function dragstart(e) {
|
||||||
|
is_user_drag = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function update_aa_status() {
|
||||||
|
if (mymap.getZoom() > zoom_level_real)
|
||||||
|
document.getElementById('mapid').classList.add("no-aa");
|
||||||
|
else
|
||||||
|
document.getElementById('mapid').classList.remove("no-aa");
|
||||||
|
}
|
||||||
|
|
||||||
|
function update_street_width() {
|
||||||
|
zoom = mymap.getZoom();
|
||||||
|
var w;
|
||||||
|
if (zoom <= 6) {
|
||||||
|
w = 2;
|
||||||
|
} else {
|
||||||
|
w = 2**zoom * 0.016 * 2;
|
||||||
|
}
|
||||||
|
var myStyle = {weight: w, opacity: 0.7};
|
||||||
|
mymap.eachLayer( function(layer) {
|
||||||
|
if ( layer.myTag && layer.myTag === "street") {
|
||||||
|
layer.setStyle(myStyle);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function update_outline_visibility() {
|
||||||
|
zoom = mymap.getZoom();
|
||||||
|
mymap.eachLayer( function(layer) {
|
||||||
|
if ( layer.myTag && layer.myTag === "outline") {
|
||||||
|
var fillOpacity;
|
||||||
|
if (zoom <= polyconf_show_cities)
|
||||||
|
fillOpacity = 0.5;
|
||||||
|
//else if (zoom == polyconf_show_cities + 1)
|
||||||
|
// opacity = 0.2;
|
||||||
|
else
|
||||||
|
fillOpacity = 0.0;
|
||||||
|
|
||||||
|
layer.setStyle({fillOpacity: fillOpacity});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function update_current_mouse_position(e) {
|
||||||
|
var lbl = document.getElementById('current_position_label');
|
||||||
|
if (e.type == "mouseout") {
|
||||||
|
lbl.parentElement.style.visibility = "hidden";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
lbl.parentElement.style.visibility = "visible";
|
||||||
|
pos = resolve_latlng(e.latlng);
|
||||||
|
lbl.innerHTML = pos.lng + "," + pos.lat;
|
||||||
|
}
|
||||||
|
|
||||||
|
mymap.on('zoomend', function () {is_user_drag = 1; update_hash_from_position();});
|
||||||
|
mymap.on('zoomend', update_aa_status);
|
||||||
|
mymap.on('zoomend', update_street_width);
|
||||||
|
mymap.on('zoomend', update_outline_visibility);
|
||||||
|
mymap.on('moveend', update_hash_from_position);
|
||||||
|
mymap.on('dragstart', function () { is_user_drag = 1;});
|
||||||
|
mymap.on('keydown', function (e) { if (e.originalEvent.code.match(/Arrow.*/)) is_user_drag = 1;});
|
||||||
|
mymap.on('overlayadd', function (e) { update_street_width(); update_outline_visibility(); });
|
||||||
|
mymap.on('mousemove', update_current_mouse_position);
|
||||||
|
mymap.on('mouseout', update_current_mouse_position);
|
||||||
|
|
||||||
|
function onHashChange(e, hash=null) {
|
||||||
|
if (!hash)
|
||||||
|
hash = document.location.hash;
|
||||||
|
if (hash == "")
|
||||||
|
return;
|
||||||
|
if (hash == "#" + get_current_location_str())
|
||||||
|
return; // We're already there
|
||||||
|
coordstr = decodeURIComponent(hash.slice(1)).split(",");
|
||||||
|
if (coordstr.length < 2)
|
||||||
|
coordstr.push(0); // Default y
|
||||||
|
if (coordstr.length < 3)
|
||||||
|
coordstr.push(mymap.getZoom()); // Default zoom
|
||||||
|
var latlng = L.latLng(parseFloat(coordstr[1]), parseFloat(coordstr[0]));
|
||||||
|
mymap.setView(latlng, parseInt(coordstr[2]));
|
||||||
|
if (coordstr.length > 3) { /* Drop named marker */
|
||||||
|
foo = L.marker(latlng).bindPopup(htmlEntities(coordstr[3])).addTo(mymap).openPopup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
build_sidebar();
|
||||||
|
|
||||||
|
window.addEventListener("hashchange", onHashChange, false);
|
||||||
|
onHashChange(null);
|
212
htdocs/streeteditor.js
Normal file
212
htdocs/streeteditor.js
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
const queryString = window.location.search;
|
||||||
|
const urlParams = new URLSearchParams(queryString);
|
||||||
|
editor_mode = urlParams.has('editor');
|
||||||
|
|
||||||
|
if (editor_mode) {
|
||||||
|
var editor_mode_polygon = urlParams.has('polygon');
|
||||||
|
|
||||||
|
var draw_layer;
|
||||||
|
var polyline;
|
||||||
|
|
||||||
|
var edit_active = 0;
|
||||||
|
|
||||||
|
var editor_style = {
|
||||||
|
radius: 8,
|
||||||
|
fillColor: "#00ff00",
|
||||||
|
color: "red",
|
||||||
|
opacity: 1.0,
|
||||||
|
fillOpacity: 0.5
|
||||||
|
};
|
||||||
|
|
||||||
|
function start_editing(dir = 1) {
|
||||||
|
// TODO: Check whether we already are in edit mode
|
||||||
|
// TODO: Detect whether we are cloner to the tail or the head, and issue Fwd or Bwd accordingly
|
||||||
|
if (polyline) {
|
||||||
|
if (dir)
|
||||||
|
polyline.editor.continueForward();
|
||||||
|
else
|
||||||
|
polyline.editor.continueBackward();
|
||||||
|
} else {
|
||||||
|
if (editor_mode_polygon)
|
||||||
|
polyline = mymap.editTools.startPolygon();
|
||||||
|
else
|
||||||
|
polyline = mymap.editTools.startPolyline();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function start_reverse_editing() {
|
||||||
|
start_editing(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function strToPoints(str) {
|
||||||
|
var temp = JSON.parse("[" + str + "]"); // TODO: add .5 everwhere
|
||||||
|
return temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
function onDragEnd(e) {
|
||||||
|
if (!polyline)
|
||||||
|
return;
|
||||||
|
var latlngs = polyline.getLatLngs();
|
||||||
|
|
||||||
|
for (var i = 0; i < latlngs.length; i++) {
|
||||||
|
latlngs[i] = resolve_latlng(latlngs[i], 1);
|
||||||
|
}
|
||||||
|
polyline.editor.refresh();
|
||||||
|
polyline.editor.refreshVertexMarkers();
|
||||||
|
location.hash = get_location_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onLoad(interactive = 1) {
|
||||||
|
if (interactive) {
|
||||||
|
str = prompt("Instructions: \n" +
|
||||||
|
"* Click the scribble-icon in the top left to start or continue drawing.\n" +
|
||||||
|
"* Click last waypoint to stop drawing.\n" +
|
||||||
|
"* Click a waypoint to delete it.\n" +
|
||||||
|
"* Click save in the top right to get the new string.\n\n" +
|
||||||
|
"Enter existing waypoints in the following format: [x,y],[x,y]:", window.location.hash.slice(1));
|
||||||
|
if (str == undefined)
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
str = window.location.hash.slice(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (polyline) {
|
||||||
|
polyline.remove(mymap);
|
||||||
|
polyline = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (str) {
|
||||||
|
coords = strToPoints(str);
|
||||||
|
for (var i = 0; i < coords.length; i++) {
|
||||||
|
coords[i] = [coords[i][1], coords[i][0]];
|
||||||
|
}
|
||||||
|
if (editor_mode_polygon)
|
||||||
|
polyline = L.polygon([coords], editor_style).addTo(mymap);
|
||||||
|
else
|
||||||
|
polyline = L.polyline(coords, editor_style).addTo(mymap);
|
||||||
|
// polyline.on('dragend', onDragEnd); // TODO: Doesn't work, see "workaround" below
|
||||||
|
polyline.enableEdit();
|
||||||
|
if (interactive) {
|
||||||
|
latlng = L.latLng(polyline_get_middle_coords(coords));
|
||||||
|
jump_to(latlng, 8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function editor_onHashChange() {
|
||||||
|
if (("#" + get_location_string()) != window.location.hash) {
|
||||||
|
onLoad(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.removeEventListener("hashchange", onHashChange, false);
|
||||||
|
window.addEventListener("hashchange", editor_onHashChange, false);
|
||||||
|
window.addEventListener("mouseup", onDragEnd, false); // Workaround as polyline.on(dragend, ) doesn't seem to work
|
||||||
|
|
||||||
|
function onMapKeydown(e) {
|
||||||
|
if (e.originalEvent.key == "Escape") {
|
||||||
|
if (polyline)
|
||||||
|
polyline.editor.cancelDrawing();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure map for better editing
|
||||||
|
mymap.setMaxZoom(14);
|
||||||
|
mymap.off('click', onMapClick);
|
||||||
|
mymap.on('keydown', onMapKeydown);
|
||||||
|
|
||||||
|
function get_location_string() {
|
||||||
|
var latlngs;
|
||||||
|
if (editor_mode_polygon)
|
||||||
|
latlngs = polyline.getLatLngs()[0];
|
||||||
|
else
|
||||||
|
latlngs = polyline.getLatLngs();
|
||||||
|
var str = "";
|
||||||
|
|
||||||
|
for (var i = 0; i < latlngs.length; i++) {
|
||||||
|
latlng = resolve_latlng(latlngs[i], 1);
|
||||||
|
if (i != 0)
|
||||||
|
str += ",";
|
||||||
|
str += "[" + (latlng.lng) + "," + (latlng.lat) + "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
function show_location_string(e) {
|
||||||
|
if (polyline)
|
||||||
|
polyline.editor.cancelDrawing();
|
||||||
|
prompt("Copy this string back into the Wiki and wait for the server to refresh the maps:", get_location_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
function reset_path(e) {
|
||||||
|
onLoad(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
L.EditControl = L.Control.extend({
|
||||||
|
options: {
|
||||||
|
position: 'topleft',
|
||||||
|
callback: null,
|
||||||
|
kind: '',
|
||||||
|
html: ''
|
||||||
|
},
|
||||||
|
onAdd: function (map) {
|
||||||
|
var container = L.DomUtil.create('div', 'leaflet-control leaflet-bar'),
|
||||||
|
link = L.DomUtil.create('a', '', container);
|
||||||
|
|
||||||
|
link.href = '#';
|
||||||
|
link.title = this.options.title;
|
||||||
|
link.innerHTML = this.options.html;
|
||||||
|
L.DomEvent.on(link, 'mousedown', L.DomEvent.stop);
|
||||||
|
L.DomEvent.on(link, 'mouseup', L.DomEvent.stop);
|
||||||
|
L.DomEvent.on(link, 'click', L.DomEvent.stop)
|
||||||
|
.on(link, 'click', function (e) {
|
||||||
|
window.LAYER = this.options.callback.call(map.editTools);
|
||||||
|
}, this);
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
L.ResetPathControl = L.EditControl.extend({
|
||||||
|
options: {
|
||||||
|
position: 'topleft',
|
||||||
|
callback: reset_path,
|
||||||
|
title: 'Enter path coordinates',
|
||||||
|
html: '📂'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
L.StartEditControl = L.EditControl.extend({
|
||||||
|
options: {
|
||||||
|
position: 'topleft',
|
||||||
|
callback: start_editing,
|
||||||
|
title: 'Start editing (forward)',
|
||||||
|
html: '\\/\\'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
L.StartReverseEditControl = L.EditControl.extend({
|
||||||
|
options: {
|
||||||
|
position: 'topleft',
|
||||||
|
callback: start_reverse_editing,
|
||||||
|
title: 'Start editing (backward)',
|
||||||
|
html: '↺'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
L.NewLineControl = L.EditControl.extend({
|
||||||
|
options: {
|
||||||
|
position: 'topleft',
|
||||||
|
callback: show_location_string,
|
||||||
|
title: 'Get location string',
|
||||||
|
html: '💾'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
mymap.addControl(new L.NewLineControl());
|
||||||
|
mymap.addControl(new L.ResetPathControl());
|
||||||
|
mymap.addControl(new L.StartEditControl());
|
||||||
|
mymap.addControl(new L.StartReverseEditControl());
|
||||||
|
|
||||||
|
onLoad();
|
||||||
|
}
|
1
lifo-dijkstraserv
Submodule
1
lifo-dijkstraserv
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit 6d252d7755287295828d768f09869b179185914d
|
@ -1,14 +0,0 @@
|
|||||||
[Unit]
|
|
||||||
Description=LIFO Dijkstraserv
|
|
||||||
After=network.target
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Type=simple
|
|
||||||
User=lifo
|
|
||||||
Group=lifo
|
|
||||||
WorkingDirectory=/opt
|
|
||||||
ExecStart=/opt/lifo-dijkstraserv /srv/http/maps.linux-forks.de/geojson/streets.json
|
|
||||||
#Restart=on-abort
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
@ -1,14 +0,0 @@
|
|||||||
server.modules += ("mod_proxy")
|
|
||||||
$HTTP["host"] == "domain.tld" {
|
|
||||||
$HTTP["url"] =~ "^/dijkstra/" {
|
|
||||||
proxy.server = ( "" =>
|
|
||||||
( "internal" =>
|
|
||||||
(
|
|
||||||
"host" => "localhost",
|
|
||||||
"port" => 6802
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"type": "Feature",
|
|
||||||
"geometry": {
|
|
||||||
"type": "LineString",
|
|
||||||
"coordinates": [
|
|
||||||
[
|
|
||||||
-856,
|
|
||||||
-305
|
|
||||||
],
|
|
||||||
[
|
|
||||||
-863,
|
|
||||||
-273
|
|
||||||
]
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"properties": {
|
|
||||||
"name": "S1:Tom Lehrer Station South"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "Feature",
|
|
||||||
"geometry": {
|
|
||||||
"type": "LineString",
|
|
||||||
"coordinates": [
|
|
||||||
[
|
|
||||||
-966,
|
|
||||||
27
|
|
||||||
],
|
|
||||||
[
|
|
||||||
-942,
|
|
||||||
44
|
|
||||||
]
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"properties": {
|
|
||||||
"name": "S1:Mueller Station East"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
]
|
|
@ -1,90 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"type": "Feature",
|
|
||||||
"geometry": {
|
|
||||||
"type": "LineString",
|
|
||||||
"coordinates": [
|
|
||||||
[
|
|
||||||
-1213,
|
|
||||||
-305
|
|
||||||
],
|
|
||||||
[
|
|
||||||
-771,
|
|
||||||
-305
|
|
||||||
],
|
|
||||||
[
|
|
||||||
-771,
|
|
||||||
-301
|
|
||||||
],
|
|
||||||
[
|
|
||||||
-645,
|
|
||||||
-101
|
|
||||||
]
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"properties": {
|
|
||||||
"name": "Tom Lehrer Street"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "Feature",
|
|
||||||
"geometry": {
|
|
||||||
"type": "LineString",
|
|
||||||
"coordinates": [
|
|
||||||
[
|
|
||||||
-852,
|
|
||||||
42
|
|
||||||
],
|
|
||||||
[
|
|
||||||
-937,
|
|
||||||
40
|
|
||||||
],
|
|
||||||
[
|
|
||||||
-1080,
|
|
||||||
42
|
|
||||||
],
|
|
||||||
[
|
|
||||||
-1087,
|
|
||||||
102
|
|
||||||
],
|
|
||||||
[
|
|
||||||
-1141,
|
|
||||||
121
|
|
||||||
]
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"properties": {
|
|
||||||
"name": "Mister Mueller Street"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "Feature",
|
|
||||||
"geometry": {
|
|
||||||
"type": "LineString",
|
|
||||||
"coordinates": [
|
|
||||||
[
|
|
||||||
-1028,
|
|
||||||
67
|
|
||||||
],
|
|
||||||
[
|
|
||||||
-1033,
|
|
||||||
-134
|
|
||||||
],
|
|
||||||
[
|
|
||||||
-1065,
|
|
||||||
-135
|
|
||||||
],
|
|
||||||
[
|
|
||||||
-1086,
|
|
||||||
-409
|
|
||||||
]
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"properties": {
|
|
||||||
"name": "Heinzi Street"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
]
|
|
@ -1,85 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"type": "Feature",
|
|
||||||
"geometry": {
|
|
||||||
"type": "LineString",
|
|
||||||
"coordinates": [
|
|
||||||
[
|
|
||||||
-967,
|
|
||||||
184
|
|
||||||
],
|
|
||||||
[
|
|
||||||
-960,
|
|
||||||
-51
|
|
||||||
],
|
|
||||||
[
|
|
||||||
-921,
|
|
||||||
-96
|
|
||||||
],
|
|
||||||
[
|
|
||||||
-878,
|
|
||||||
-111
|
|
||||||
],
|
|
||||||
[
|
|
||||||
-866,
|
|
||||||
-151
|
|
||||||
],
|
|
||||||
[
|
|
||||||
-867,
|
|
||||||
-213
|
|
||||||
],
|
|
||||||
[
|
|
||||||
-863,
|
|
||||||
-243
|
|
||||||
],
|
|
||||||
[
|
|
||||||
-863,
|
|
||||||
-309
|
|
||||||
],
|
|
||||||
[
|
|
||||||
-864,
|
|
||||||
-405
|
|
||||||
],
|
|
||||||
[
|
|
||||||
-864,
|
|
||||||
-551
|
|
||||||
],
|
|
||||||
[
|
|
||||||
-802,
|
|
||||||
-624
|
|
||||||
],
|
|
||||||
[
|
|
||||||
-688,
|
|
||||||
-662
|
|
||||||
],
|
|
||||||
[
|
|
||||||
-566,
|
|
||||||
-620
|
|
||||||
],
|
|
||||||
[
|
|
||||||
-522,
|
|
||||||
-534
|
|
||||||
],
|
|
||||||
[
|
|
||||||
-347,
|
|
||||||
-483
|
|
||||||
],
|
|
||||||
[
|
|
||||||
-126,
|
|
||||||
-467
|
|
||||||
],
|
|
||||||
[
|
|
||||||
36,
|
|
||||||
-472
|
|
||||||
]
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"properties": {
|
|
||||||
"name": "S1",
|
|
||||||
"line": "S1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
]
|
|
66
scripts/convert_maps.sh
Executable file
66
scripts/convert_maps.sh
Executable file
@ -0,0 +1,66 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Convert a huge .png file into smaller chunks for LeafletJS.
|
||||||
|
#
|
||||||
|
# Copyright (C) 2020 Markus Koch <markus@notsyncing.net>
|
||||||
|
#
|
||||||
|
# This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
|
||||||
|
# You can obtain a copy of the license at https://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
# TODO: The width of the input png has to be a multiple of TILESIZE, otherwise bad things will happen.
|
||||||
|
# Workaround for now is to add white borders on the bottom and right:
|
||||||
|
# `convert $MAPFILE -extent ${crop}x${crop} -gravity NorthWest $MAPFILE.scaled.png`
|
||||||
|
|
||||||
|
TILESIZE=256
|
||||||
|
MAPNAME="world-2020-04-09"
|
||||||
|
MAPFILE="../$MAPNAME.png"
|
||||||
|
|
||||||
|
width=`file "$MAPFILE" | sed -n "s/.* \([0-9]\+\) x \([0-9]\+\).*/\1/p"`
|
||||||
|
crop=$TILESIZE
|
||||||
|
zoom=0
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
out="$MAPNAME/$zoom"
|
||||||
|
tempfile=$out/temp.png
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Generating maps for zoomlevel $zoom to $out..."
|
||||||
|
|
||||||
|
mkdir -p $out;
|
||||||
|
|
||||||
|
if [ ! -f "$tempfile" ]; then
|
||||||
|
if [ $crop -ge $width ]; then
|
||||||
|
echo " Reached max zoom at zoomlevel $zoom [using original zoom]"
|
||||||
|
cp $MAPFILE $tempfile
|
||||||
|
#convert $MAPFILE -extent ${crop}x${crop} -gravity NorthWest $tempfile
|
||||||
|
else
|
||||||
|
echo " Scaling map for zoomlevel $zoom"
|
||||||
|
convert $MAPFILE -resize ${crop}x${crop} $tempfile
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo " Reusing existing scaled image"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo " Generating tiles..."
|
||||||
|
convert $tempfile -crop ${TILESIZE}x${TILESIZE} +adjoin $out/%d.png
|
||||||
|
|
||||||
|
rm $tempfile
|
||||||
|
|
||||||
|
if [ $crop -ge $width ]; then
|
||||||
|
break;
|
||||||
|
fi
|
||||||
|
crop=$(($crop * 2))
|
||||||
|
zoom=$(($zoom + 1))
|
||||||
|
done;
|
||||||
|
|
||||||
|
echo "Renaming files..."
|
||||||
|
for (( z=0; z<=$zoom; z++ )) {
|
||||||
|
echo "Zoom level $z"
|
||||||
|
fac=$((2**$z))
|
||||||
|
for (( y=0; y<$fac; y++ )) {
|
||||||
|
outdir="$MAPNAME/$z/$y"
|
||||||
|
mkdir $outdir
|
||||||
|
for (( x=0; x<$fac; x++ )) {
|
||||||
|
mv $MAPNAME/$z/$(($fac * y + $x)).png $outdir/$x.png
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
35
scripts/geojson/fetch_all_points.sh
Executable file
35
scripts/geojson/fetch_all_points.sh
Executable file
@ -0,0 +1,35 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
json=`curl -s 'https://wiki.linux-forks.de/mediawiki/api.php?action=query&format=json&list=search&redirects=1&converttitles=1&srsearch=%7B%7BCo%7C&srlimit=max&srwhat=text&srprop=snippet'`
|
||||||
|
data=`echo "$json" | json_reformat`;
|
||||||
|
|
||||||
|
echo "[";
|
||||||
|
|
||||||
|
IFS=$'\n';
|
||||||
|
for line in $data; do
|
||||||
|
temp=`echo "$line" | sed -n 's/^\s*{\s*$/END/p'`
|
||||||
|
if [[ "$temp" == "END" ]]; then
|
||||||
|
title="";
|
||||||
|
coords="";
|
||||||
|
fi
|
||||||
|
temp=`echo "$line" | sed -n 's/\s*"title": "*\([^"]\+\).*/\1/p'`
|
||||||
|
if [[ "$temp" != "" ]]; then
|
||||||
|
title="$temp";
|
||||||
|
fi
|
||||||
|
temp=`echo "$line" | sed -n 's#.*<span class=.searchmatch.>Co<\/span>|\([0-9\-]\+\)|\([0-9\-]\+\)|\([0-9\-]\+\).*#\1,\3#p'`
|
||||||
|
if [[ "$temp" != "" ]]; then
|
||||||
|
coords="$temp";
|
||||||
|
fi
|
||||||
|
if [[ "$title" != "" && "$coords" != "" ]]; then
|
||||||
|
echo "{\"type\": \"Feature\", \"properties\": {\"name\": \"$title\","
|
||||||
|
urltitle=`echo "$title" | sed 's/ /_/g'`
|
||||||
|
./get_rendered_meta.sh "https://wiki.linux-forks.de/mediawiki/index.php/$urltitle"
|
||||||
|
echo "},";
|
||||||
|
echo "\"geometry\": {\"type\": \"Point\", \"coordinates\": [$coords]}},";
|
||||||
|
title="";
|
||||||
|
coords="";
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
IFS=" ";
|
||||||
|
|
||||||
|
echo "{}]";
|
49
scripts/geojson/get_rendered_meta.sh
Executable file
49
scripts/geojson/get_rendered_meta.sh
Executable file
@ -0,0 +1,49 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
BASE="https://wiki.linux-forks.de"
|
||||||
|
|
||||||
|
mode="find_description"
|
||||||
|
|
||||||
|
thumbnail=""
|
||||||
|
categories=""
|
||||||
|
|
||||||
|
data=`curl -s "$1"`
|
||||||
|
|
||||||
|
temp=`echo "$data" | sed -n 's/<p>\(.\+\).*/\1/p' | head -n1`
|
||||||
|
if [ "$temp" != "" ]; then
|
||||||
|
temp=`echo "$temp" | sed "s#href=\"#href=\"$BASE#g" | sed 's/"/\\\\"/g' | sed 's/\t//g'`
|
||||||
|
description="$temp"
|
||||||
|
mode="find_infobox";
|
||||||
|
fi
|
||||||
|
|
||||||
|
IFS=$'>';
|
||||||
|
for line in $data; do
|
||||||
|
if [ "$mode" == "find_infobox" ]; then
|
||||||
|
if [ "`echo \"$line\" | grep 'infobox'`" != "" ]; then
|
||||||
|
mode="image";
|
||||||
|
fi
|
||||||
|
elif [ "$mode" == "image" ]; then
|
||||||
|
temp=`echo "$line" | sed -n 's/.*img.*src="\([^"]\+\).*/\1/p'`;
|
||||||
|
if [ "$temp" != "" ]; then
|
||||||
|
thumbnail="$BASE$temp"
|
||||||
|
mode="find_cat"
|
||||||
|
fi
|
||||||
|
elif [ "$mode" == "find_cat" ]; then
|
||||||
|
if [ "`echo \"$line\" | grep 'mw-normal-catlinks'`" != "" ]; then
|
||||||
|
mode="cat";
|
||||||
|
fi
|
||||||
|
elif [ "$mode" == "cat" ]; then
|
||||||
|
temp=`echo "$line" | sed -n 's/.*title="Category:\([^"]\+\).*/\1/pg' | grep -v 'page does not exist'`
|
||||||
|
if [ "$temp" != "" ]; then
|
||||||
|
if [ "$categories" != "" ]; then
|
||||||
|
categories="$categories,"
|
||||||
|
fi
|
||||||
|
categories="$categories\"$temp\""
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
IFS=" ";
|
||||||
|
|
||||||
|
echo "\"categories\": [$categories],"
|
||||||
|
echo "\"image\": \"$thumbnail\","
|
||||||
|
echo "\"description\": \"$description\""
|
32
scripts/geojson/maps/fetch_city_outlines.sh
Executable file
32
scripts/geojson/maps/fetch_city_outlines.sh
Executable file
@ -0,0 +1,32 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
json=`curl 'https://wiki.linux-forks.de/mediawiki/api.php?action=query&prop=revisions&rvprop=content&format=json&titles=Maps:Cities'`
|
||||||
|
data=`echo "$json" | json_reformat | sed -e 's/\\\\n//g' -n -e 's/begin:mapdata\([^}]\+\)/\1/gp' | sed -e "s/|-|/}/g"`
|
||||||
|
|
||||||
|
echo "["
|
||||||
|
export IFS="}"
|
||||||
|
for entry in $data; do
|
||||||
|
name=`echo "$entry" | sed -n 's/\s*\([^|]\+\).*/\1/p' | sed 's/ $//'`
|
||||||
|
type=`echo "$entry" | sed -n 's/\s*\([^|]\+\)||\s*\([^|]\+\).*/\2/p' | sed 's/ $//'`
|
||||||
|
coord=`echo "$entry" | sed -n 's/.*||\s*\([^|]\+\).*/\1/p'`
|
||||||
|
if [[ "$type" == "district" ]]; then
|
||||||
|
continue;
|
||||||
|
fi
|
||||||
|
if [[ "$name" != "" && "$coord" != "" ]]; then
|
||||||
|
echo "{ \"type\": \"Feature\",
|
||||||
|
\"geometry\": {
|
||||||
|
\"type\": \"Polygon\",
|
||||||
|
\"coordinates\": [[
|
||||||
|
$coord
|
||||||
|
]]
|
||||||
|
},
|
||||||
|
\"properties\": {
|
||||||
|
\"name\": \"$name\",
|
||||||
|
\"type\": \"$type\"
|
||||||
|
}
|
||||||
|
},"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
export IFS=" "
|
||||||
|
echo "{}"
|
||||||
|
echo "]"
|
29
scripts/geojson/maps/fetch_streets.sh
Executable file
29
scripts/geojson/maps/fetch_streets.sh
Executable file
@ -0,0 +1,29 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# WARNING: This script is not yet production ready. The slightest error in the wikipage can throw it off. Handle with care.
|
||||||
|
|
||||||
|
json=`curl 'https://wiki.linux-forks.de/mediawiki/api.php?action=query&prop=revisions&rvprop=content&format=json&titles=Maps:Streets'`
|
||||||
|
data=`echo "$json" | json_reformat | sed -e 's/\\\\n//g' -n -e 's/begin:mapdata\([^}]\+\)/\1/gp' | sed -e "s/|-|/}/g"`
|
||||||
|
|
||||||
|
echo "["
|
||||||
|
export IFS="}"
|
||||||
|
for entry in $data; do
|
||||||
|
name=`echo "$entry" | sed -n 's/\s*\([^|]\+\).*/\1/p' | sed 's/ $//'`
|
||||||
|
coord=`echo "$entry" | sed -n 's/.*||\s*\([^|]\+\).*/\1/p'`
|
||||||
|
if [[ "$name" != "" && "$coord" != "" ]]; then
|
||||||
|
echo "{ \"type\": \"Feature\",
|
||||||
|
\"geometry\": {
|
||||||
|
\"type\": \"LineString\",
|
||||||
|
\"coordinates\": [
|
||||||
|
$coord
|
||||||
|
]
|
||||||
|
},
|
||||||
|
\"properties\": {
|
||||||
|
\"name\": \"$name\"
|
||||||
|
}
|
||||||
|
},"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
export IFS=" "
|
||||||
|
echo "{}"
|
||||||
|
echo "]"
|
10
scripts/geojson/maps/update_all.sh
Executable file
10
scripts/geojson/maps/update_all.sh
Executable file
@ -0,0 +1,10 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
function fetch() {
|
||||||
|
"./fetch_$1.sh" | json_reformat -m > "$1.json.tmp"
|
||||||
|
rm -f "$1.json"
|
||||||
|
mv "$1.json.tmp" "$1.json"
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch streets
|
||||||
|
fetch city_outlines
|
@ -1,594 +0,0 @@
|
|||||||
#include "dijkstragraph.h"
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <glib/glist.h>
|
|
||||||
#include <math.h>
|
|
||||||
#include "logging.h"
|
|
||||||
|
|
||||||
#define FP_THRESHOLD 0.01
|
|
||||||
|
|
||||||
int dijkstra_node_same (struct dijkstra_node *node_a,
|
|
||||||
struct dijkstra_node *node_b)
|
|
||||||
{
|
|
||||||
int same = 1;
|
|
||||||
|
|
||||||
// Not the proper way to do it, but it works for now.
|
|
||||||
same &= (node_a->net_meta == node_b->net_meta);
|
|
||||||
same &= (fabs(node_a->position.x - node_b->position.x) < FP_THRESHOLD);
|
|
||||||
same &= (fabs(node_a->position.y - node_b->position.y) < FP_THRESHOLD);
|
|
||||||
same &= (node_a->position.z == node_b->position.z);
|
|
||||||
|
|
||||||
return same;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
* \brief dijkstra_node_new creates a new dijkstra_node
|
|
||||||
* \param x x position
|
|
||||||
* \param y y position
|
|
||||||
* \return a new dijkstra node or NULL. Needs to be freed manually.
|
|
||||||
*/
|
|
||||||
struct dijkstra_node *dijkstra_node_new(float x,
|
|
||||||
float y,
|
|
||||||
int z,
|
|
||||||
int uid,
|
|
||||||
struct dijkstra_net_meta *net_meta)
|
|
||||||
{
|
|
||||||
struct dijkstra_node *node;
|
|
||||||
|
|
||||||
node = malloc(sizeof(*node));
|
|
||||||
if (node) {
|
|
||||||
node->position.x = x;
|
|
||||||
node->position.y = y;
|
|
||||||
node->position.z = z;
|
|
||||||
node->uid = uid;
|
|
||||||
node->net_meta = net_meta;
|
|
||||||
node->paths = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct dijkstra_node *dijkstra_node_exists(GList *list,
|
|
||||||
struct dijkstra_node *node)
|
|
||||||
{
|
|
||||||
GList *l;
|
|
||||||
struct dijkstra_node *node_existing;
|
|
||||||
|
|
||||||
for (l = list; l != NULL; l = l->next) {
|
|
||||||
node_existing = (struct dijkstra_node*) l->data;
|
|
||||||
if (dijkstra_node_same(node, node_existing)) {
|
|
||||||
return node_existing;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
* \brief dijkstra_node_new_to_list creates a new node entry if it does not already exist in the list
|
|
||||||
* \param list list of dijkstra_node
|
|
||||||
* \param x
|
|
||||||
* \param y
|
|
||||||
* \param is_new When != NULL: is_new will be set to true if a new node had to be created
|
|
||||||
* \return a dijkstra_node with the specified position (either newly created or from the list). Or NULL on error.
|
|
||||||
*/
|
|
||||||
struct dijkstra_node *dijkstra_node_new_to_list(GList **list,
|
|
||||||
float x,
|
|
||||||
float y,
|
|
||||||
int z,
|
|
||||||
struct dijkstra_net_meta *net_meta)
|
|
||||||
{
|
|
||||||
struct dijkstra_node *node;
|
|
||||||
struct dijkstra_node *found;
|
|
||||||
int uid;
|
|
||||||
|
|
||||||
uid = g_list_length(*list);
|
|
||||||
|
|
||||||
node = dijkstra_node_new(x, y, z, uid, net_meta);
|
|
||||||
if (!node)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
if ((found = dijkstra_node_exists(*list, node))) {
|
|
||||||
free(node);
|
|
||||||
node = found;
|
|
||||||
} else {
|
|
||||||
*list = g_list_append(*list, node);
|
|
||||||
}
|
|
||||||
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
|
|
||||||
int dijkstra_connect_destination_node_to_path(struct dijkstra_node *destination,
|
|
||||||
struct dijkstra_path *path)
|
|
||||||
{
|
|
||||||
if (!path || !destination)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
if (path->destination) {
|
|
||||||
report(LL_WARNING, "Connecting an already connected path (destination).");
|
|
||||||
}
|
|
||||||
destination->paths = g_list_append(destination->paths, (gpointer) path);
|
|
||||||
path->destination = destination;
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int dijkstra_connect_source_node_to_path(struct dijkstra_node *source,
|
|
||||||
struct dijkstra_path *path)
|
|
||||||
{
|
|
||||||
if (!path || !source)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
if (path->source) {
|
|
||||||
report(LL_WARNING, "Connecting an already connected path (source).");
|
|
||||||
}
|
|
||||||
|
|
||||||
source->paths = g_list_append(source->paths, (gpointer) path);
|
|
||||||
path->source = source;
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
* \brief dijkstra_connect_nodes_to_path connect the two nodes using the path
|
|
||||||
* \param source
|
|
||||||
* \param destination
|
|
||||||
* \param path
|
|
||||||
* \return
|
|
||||||
*/
|
|
||||||
int dijkstra_connect_nodes_to_path(struct dijkstra_node *source,
|
|
||||||
struct dijkstra_node *destination,
|
|
||||||
struct dijkstra_path *path)
|
|
||||||
{
|
|
||||||
int ret = 0;
|
|
||||||
|
|
||||||
ret |= dijkstra_connect_source_node_to_path(source, path);
|
|
||||||
ret |= dijkstra_connect_destination_node_to_path(destination, path);
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Creates an unlinked (!) copy of the path
|
|
||||||
struct dijkstra_path *dijkstra_path_dup_shallow(struct dijkstra_path *path)
|
|
||||||
{
|
|
||||||
struct dijkstra_path *dup;
|
|
||||||
|
|
||||||
if (!path)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
dup = malloc(sizeof(*path));
|
|
||||||
if (dup) {
|
|
||||||
memcpy(dup, path, sizeof(*path));
|
|
||||||
dup->source = NULL;
|
|
||||||
dup->destination = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
return dup;
|
|
||||||
}
|
|
||||||
|
|
||||||
float dijkstra_position_get_distance(struct position *position_a,
|
|
||||||
struct position *position_b) {
|
|
||||||
return sqrt(pow(position_a->x - position_b->x, 2) +
|
|
||||||
pow(position_a->y - position_b->y, 2));
|
|
||||||
}
|
|
||||||
|
|
||||||
dijkstra_cost dijkstra_get_weight_from_distance(struct dijkstra_node *node_a,
|
|
||||||
struct dijkstra_node *node_b,
|
|
||||||
enum dijkstra_net_type net_type)
|
|
||||||
{
|
|
||||||
float dist;
|
|
||||||
float scale;
|
|
||||||
|
|
||||||
switch (net_type) {
|
|
||||||
case TRAINLINE:
|
|
||||||
scale = 10.0;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
scale = 1.0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
dist = dijkstra_position_get_distance(&node_a->position, &node_b->position);
|
|
||||||
dist /= scale;
|
|
||||||
|
|
||||||
if (dist < DIJKSTRA_COST_MIN)
|
|
||||||
dist = DIJKSTRA_COST_MIN;
|
|
||||||
|
|
||||||
return (dijkstra_cost) (dist * 100);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
* \brief dijkstra_path_new creates a new dijkstra_path between two nodes.
|
|
||||||
* \param name the name of the path (pointer is not copied and must stay valid!)
|
|
||||||
* \return a new dijkstra path or NULL. Needs to be freed manually.
|
|
||||||
*/
|
|
||||||
struct dijkstra_path *dijkstra_path_new(char *name,
|
|
||||||
struct dijkstra_node *source,
|
|
||||||
struct dijkstra_node *destination,
|
|
||||||
dijkstra_cost weight,
|
|
||||||
struct dijkstra_net_meta *net_meta)
|
|
||||||
{
|
|
||||||
struct dijkstra_path *path;
|
|
||||||
|
|
||||||
path = malloc(sizeof(*path));
|
|
||||||
if (path) {
|
|
||||||
path->name = name;
|
|
||||||
if (weight == DIJKSTRA_WEIGHT_AUTO)
|
|
||||||
path->weight = dijkstra_get_weight_from_distance(source,
|
|
||||||
destination,
|
|
||||||
net_meta->type);
|
|
||||||
else
|
|
||||||
path->weight = weight;
|
|
||||||
|
|
||||||
path->source = NULL;
|
|
||||||
path->destination = NULL;
|
|
||||||
|
|
||||||
path->net_meta = net_meta;
|
|
||||||
|
|
||||||
if (dijkstra_connect_nodes_to_path(source, destination, path) != 0) {
|
|
||||||
report(LL_CRITICAL, "Connect nodes failed: %p with %p as %p (%s)",
|
|
||||||
source, destination, path, path->name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct dijkstra_node *dijkstra_disconnect_node_from_path(struct dijkstra_path *path,
|
|
||||||
struct dijkstra_node *node)
|
|
||||||
{
|
|
||||||
if (!g_list_find(node->paths, path)) {
|
|
||||||
report(LL_CRITICAL, "Tried disconnecting node %p from path %p (%s) that wasn't connected in the first place.",
|
|
||||||
node, path, path->name);
|
|
||||||
}
|
|
||||||
node->paths = g_list_remove(node->paths, path);
|
|
||||||
|
|
||||||
if (path->source == path->destination) {
|
|
||||||
report(LL_CRITICAL, "Zero-length path. Destination node = source node for path %p", path);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (path->source == node) {
|
|
||||||
path->source = NULL;
|
|
||||||
}
|
|
||||||
if (path->destination == node) {
|
|
||||||
path->destination = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
return node;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void checkycheckcheck(struct dijkstra_solver *solver)
|
|
||||||
{
|
|
||||||
GList *l;
|
|
||||||
struct dijkstra_node *node;
|
|
||||||
struct dijkstra_path *path;
|
|
||||||
|
|
||||||
report(LL_INFO, "");
|
|
||||||
for (l = solver->nodes; l != NULL; l = l->next) {
|
|
||||||
node = (struct dijkstra_node*) l->data;
|
|
||||||
report(LL_INFO, "Node %5d @%p: %f,\t%f [%d paths]", node->uid, node, node->position.x, node->position.y, g_list_length(node->paths));
|
|
||||||
|
|
||||||
for (GList *m = node->paths; m != NULL; m = m->next) {
|
|
||||||
path = (struct dijkstra_path*) m->data;
|
|
||||||
struct dijkstra_node *foo = dijkstra_node_get_connection(node, path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int dijkstra_path_intersect(struct dijkstra_solver *solver,
|
|
||||||
struct dijkstra_path *path_a,
|
|
||||||
struct dijkstra_path *path_b)
|
|
||||||
{
|
|
||||||
// Yes, I was too lazy to code this myself:
|
|
||||||
// https://stackoverflow.com/questions/563198/how-do-you-detect-where-two-line-segments-intersect
|
|
||||||
|
|
||||||
struct dijkstra_path *path_new;
|
|
||||||
struct dijkstra_node *node_new;
|
|
||||||
struct dijkstra_node *disconnected_node;
|
|
||||||
struct dijkstra_path *access_path = NULL;
|
|
||||||
struct dijkstra_path *target_path = NULL;
|
|
||||||
|
|
||||||
float p0_x, p0_y, p1_x, p1_y;
|
|
||||||
float p2_x, p2_y, p3_x, p3_y;
|
|
||||||
int p0_z, p1_z, p2_z, p3_z;
|
|
||||||
float i_x, i_y;
|
|
||||||
int i_z;
|
|
||||||
|
|
||||||
float s1_x, s1_y, s2_x, s2_y;
|
|
||||||
float s, t;
|
|
||||||
|
|
||||||
if (!path_a || !path_b)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
if (path_a->net_meta->type == ACCESS) {
|
|
||||||
access_path = path_a;
|
|
||||||
target_path = path_b;
|
|
||||||
} else if (path_b->net_meta->type == ACCESS) {
|
|
||||||
access_path = path_b;
|
|
||||||
target_path = path_a;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (access_path) {
|
|
||||||
if (target_path->net_meta->type != STREET &&
|
|
||||||
!strstr(access_path->net_meta->access, target_path->name)) {
|
|
||||||
return 0; // Don't intersect if access != name of target
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (path_a->net_meta->type != path_b->net_meta->type)
|
|
||||||
return 0; // Never intersect two layers of different types, unless it's through access layers
|
|
||||||
// path_a and path_b types are now always guaranteed to be the same
|
|
||||||
if (path_a->net_meta->type == TRAINLINE)
|
|
||||||
return 0; // Never intersect trainlines (apart from with access types)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!target_path)
|
|
||||||
target_path = path_b; // Needed for net_meta selection below
|
|
||||||
|
|
||||||
// Check height
|
|
||||||
p0_z = path_a->source->position.z;
|
|
||||||
p1_z = path_a->destination->position.z;
|
|
||||||
p2_z = path_b->source->position.z;
|
|
||||||
p3_z = path_b->destination->position.z;
|
|
||||||
|
|
||||||
if (!access_path && // For access paths, z is invalid, so only check this if we are intersecting streets (trainlines are filtered out above)
|
|
||||||
!(p0_z == p1_z && p1_z == p2_z && p2_z == p3_z)) // We don't want to intersect on height, they must match exactly for all four points.
|
|
||||||
return 0; // No collision
|
|
||||||
|
|
||||||
p0_x = path_a->source->position.x;
|
|
||||||
p0_y = path_a->source->position.y;
|
|
||||||
p1_x = path_a->destination->position.x;
|
|
||||||
p1_y = path_a->destination->position.y;
|
|
||||||
|
|
||||||
p2_x = path_b->source->position.x;
|
|
||||||
p2_y = path_b->source->position.y;
|
|
||||||
p3_x = path_b->destination->position.x;
|
|
||||||
p3_y = path_b->destination->position.y;
|
|
||||||
|
|
||||||
s1_x = p1_x - p0_x;
|
|
||||||
s1_y = p1_y - p0_y;
|
|
||||||
|
|
||||||
s2_x = p3_x - p2_x;
|
|
||||||
s2_y = p3_y - p2_y;
|
|
||||||
|
|
||||||
s = (-s1_y * (p0_x - p2_x) + s1_x * (p0_y - p2_y)) / (-s2_x * s1_y + s1_x * s2_y);
|
|
||||||
t = ( s2_x * (p0_y - p2_y) - s2_y * (p0_x - p2_x)) / (-s2_x * s1_y + s1_x * s2_y);
|
|
||||||
|
|
||||||
if (s >= 0 && s <= 1 && t >= 0 && t <= 1)
|
|
||||||
{
|
|
||||||
// Collision detected
|
|
||||||
i_x = p0_x + (t * s1_x);
|
|
||||||
i_y = p0_y + (t * s1_y);
|
|
||||||
i_z = p0_z; // They must all on the same height when intersecting, so we can pick whichever.
|
|
||||||
|
|
||||||
node_new = dijkstra_node_new_to_list(&solver->nodes, i_x, i_y, i_z, target_path->source->net_meta);
|
|
||||||
if (node_new != path_a->source && node_new != path_a->destination) {
|
|
||||||
path_new = dijkstra_path_dup_shallow(path_a);
|
|
||||||
solver->paths = g_list_append(solver->paths, (gpointer) path_new);
|
|
||||||
|
|
||||||
disconnected_node = dijkstra_disconnect_node_from_path(path_a, path_a->destination);
|
|
||||||
dijkstra_connect_destination_node_to_path(node_new, path_a);
|
|
||||||
path_a->weight = dijkstra_get_weight_from_distance(
|
|
||||||
path_a->source,
|
|
||||||
path_a->destination,
|
|
||||||
path_new->net_meta->type);
|
|
||||||
|
|
||||||
dijkstra_connect_nodes_to_path(node_new, disconnected_node, path_new);
|
|
||||||
path_new->weight = dijkstra_get_weight_from_distance(
|
|
||||||
path_new->source,
|
|
||||||
path_new->destination,
|
|
||||||
path_new->net_meta->type);
|
|
||||||
|
|
||||||
dijkstra_path_intersect_all(solver, path_b, path_new); // Since straight paths can only cross once, we don't need to check any newly created entries.
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now do the same thing for path b
|
|
||||||
if (node_new != path_b->source && node_new != path_b->destination) {
|
|
||||||
path_new = dijkstra_path_dup_shallow(path_b);
|
|
||||||
solver->paths = g_list_append(solver->paths, (gpointer) path_new);
|
|
||||||
|
|
||||||
disconnected_node = dijkstra_disconnect_node_from_path(path_b, path_b->destination);
|
|
||||||
dijkstra_connect_destination_node_to_path(node_new, path_b);
|
|
||||||
path_b->weight = dijkstra_get_weight_from_distance(
|
|
||||||
path_b->source,
|
|
||||||
path_b->destination,
|
|
||||||
path_new->net_meta->type);
|
|
||||||
|
|
||||||
dijkstra_connect_nodes_to_path(node_new, disconnected_node, path_new);
|
|
||||||
path_new->weight = dijkstra_get_weight_from_distance(path_new->source,
|
|
||||||
path_new->destination,
|
|
||||||
path_new->net_meta->type);
|
|
||||||
|
|
||||||
dijkstra_path_intersect_all(solver, path_a, path_new);
|
|
||||||
}
|
|
||||||
|
|
||||||
// In case neither if matches that means we are on the corner where two paths intersect at their ends.
|
|
||||||
// No need to do anything then, dijkstra_node_new_to_list will have taken care of everything.
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0; // No collision
|
|
||||||
}
|
|
||||||
|
|
||||||
void dijkstra_path_intersect_all(struct dijkstra_solver *solver,
|
|
||||||
struct dijkstra_path *path,
|
|
||||||
struct dijkstra_path *path_last)
|
|
||||||
{
|
|
||||||
GList *l;
|
|
||||||
struct dijkstra_path *existing_path;
|
|
||||||
|
|
||||||
for (l = solver->paths; l != NULL; l = l->next) {
|
|
||||||
existing_path = (struct dijkstra_path*) l->data;
|
|
||||||
dijkstra_path_intersect(solver, path, existing_path);
|
|
||||||
if (l->data == path_last) /* Avoid testing newly added paths */
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct dijkstra_path *dijkstra_path_new_to_list(struct dijkstra_solver *solver,
|
|
||||||
float x1,
|
|
||||||
float y1,
|
|
||||||
float x2,
|
|
||||||
float y2,
|
|
||||||
int z1,
|
|
||||||
int z2,
|
|
||||||
char *name,
|
|
||||||
struct dijkstra_net_meta *net_meta,
|
|
||||||
dijkstra_cost weight)
|
|
||||||
{
|
|
||||||
struct dijkstra_node *n1, *n2;
|
|
||||||
struct dijkstra_path *path;
|
|
||||||
GList *l_last;
|
|
||||||
struct dijkstra_path *path_last = NULL;
|
|
||||||
|
|
||||||
n1 = dijkstra_node_new_to_list(&solver->nodes, x1, y1, z1, net_meta);
|
|
||||||
n2 = dijkstra_node_new_to_list(&solver->nodes, x2, y2, z2, net_meta);
|
|
||||||
path = dijkstra_path_new(name, n1, n2, weight, net_meta);
|
|
||||||
|
|
||||||
l_last = g_list_last(solver->paths);
|
|
||||||
if (l_last)
|
|
||||||
path_last = l_last->data;
|
|
||||||
solver->paths = g_list_append(solver->paths, (gpointer) path);
|
|
||||||
|
|
||||||
dijkstra_path_intersect_all(solver, path, path_last);
|
|
||||||
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct dijkstra_solver *dijkstra_solver_new()
|
|
||||||
{
|
|
||||||
struct dijkstra_solver *solver;
|
|
||||||
|
|
||||||
solver = malloc(sizeof(*solver));
|
|
||||||
if (solver) {
|
|
||||||
solver->nodes = NULL;
|
|
||||||
solver->paths = NULL;
|
|
||||||
solver->net_metas = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
return solver;
|
|
||||||
}
|
|
||||||
|
|
||||||
void dijkstra_solver_free(struct dijkstra_solver *solver)
|
|
||||||
{
|
|
||||||
GList *l;
|
|
||||||
struct dijkstra_path *path;
|
|
||||||
struct dijkstra_node *node;
|
|
||||||
struct dijkstra_net_meta *net_meta;
|
|
||||||
|
|
||||||
// Free memory
|
|
||||||
for (l = solver->paths; l != NULL; l = l->next) {
|
|
||||||
path = (struct dijkstra_path*) l->data;
|
|
||||||
// TODO: free(path->name);
|
|
||||||
// Currently, we reuse the existing memory block when for the
|
|
||||||
// name when cloning streets. As such, clearing it here will
|
|
||||||
// cause a double free if the street has been split.
|
|
||||||
free(path);
|
|
||||||
}
|
|
||||||
g_list_free(solver->paths);
|
|
||||||
|
|
||||||
for (l = solver->nodes; l != NULL; l = l->next) {
|
|
||||||
node = (struct dijkstra_node*) l->data;
|
|
||||||
free(node);
|
|
||||||
}
|
|
||||||
g_list_free(solver->nodes);
|
|
||||||
|
|
||||||
for (l = solver->net_metas; l != NULL; l = l->next) {
|
|
||||||
net_meta = (struct dijkstra_net_meta*) l->data;
|
|
||||||
free(net_meta->name);
|
|
||||||
if (net_meta->access)
|
|
||||||
free(net_meta->access);
|
|
||||||
free(net_meta);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inline struct dijkstra_node *dijkstra_node_get_connection(struct dijkstra_node *node,
|
|
||||||
struct dijkstra_path *path)
|
|
||||||
{
|
|
||||||
if (!node || !path) {
|
|
||||||
report(LL_CRITICAL, "Called get_connection with NULL node (%p) or path (%p).",
|
|
||||||
node, path);
|
|
||||||
}
|
|
||||||
if (node != path->destination && node != path->source) {
|
|
||||||
report(LL_WARNING, "get_connection requested for invalid node / path pair: %d with %p (%s).", node->uid, path, path->name);
|
|
||||||
for (GList *l = node->paths; l != NULL; l = l->next) {
|
|
||||||
struct dijkstra_path *path = (struct dijkstra_path*) l->data;
|
|
||||||
report(LL_WARNING, " Available: %p (%s)", path, path->name);
|
|
||||||
}
|
|
||||||
getchar();
|
|
||||||
}
|
|
||||||
return (path->destination == node ? path->source : path->destination);
|
|
||||||
}
|
|
||||||
|
|
||||||
struct dijkstra_node *dijkstra_node_find_closest_node(struct dijkstra_solver *solver,
|
|
||||||
struct position *position) {
|
|
||||||
GList *l = NULL;
|
|
||||||
GList *pl = NULL;
|
|
||||||
struct dijkstra_node *node = NULL;
|
|
||||||
struct dijkstra_node *closest_node = NULL;
|
|
||||||
struct dijkstra_path *path = NULL;
|
|
||||||
float closest_distance;
|
|
||||||
float distance;
|
|
||||||
|
|
||||||
for (l = solver->nodes; l != NULL; l = l->next) {
|
|
||||||
node = (struct dijkstra_node*) l->data;
|
|
||||||
for (pl = node->paths; pl != NULL; pl = pl->next) {
|
|
||||||
path = (struct dijkstra_path*) pl->data;
|
|
||||||
if (path->net_meta->type == STREET)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (!(path && path->net_meta->type == STREET))
|
|
||||||
continue; // Only start on nodes that are part of a street
|
|
||||||
distance = dijkstra_position_get_distance(position, &node->position);
|
|
||||||
if (!closest_node || distance < closest_distance) {
|
|
||||||
closest_node = node;
|
|
||||||
closest_distance = distance;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return closest_node;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct dijkstra_net_meta *dijkstra_net_meta_new(enum dijkstra_net_type net_type,
|
|
||||||
char *name,
|
|
||||||
char *access)
|
|
||||||
{
|
|
||||||
struct dijkstra_net_meta *net_meta;
|
|
||||||
|
|
||||||
net_meta = malloc(sizeof(*net_meta));
|
|
||||||
if (net_meta) {
|
|
||||||
net_meta->type = net_type;
|
|
||||||
net_meta->name = strdup(name);
|
|
||||||
if (access)
|
|
||||||
net_meta->access = strdup(access);
|
|
||||||
else
|
|
||||||
net_meta->access = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
return net_meta;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct dijkstra_net_meta *dijkstra_net_meta_new_to_list(struct dijkstra_solver *solver,
|
|
||||||
enum dijkstra_net_type net_type,
|
|
||||||
char *name,
|
|
||||||
char *access)
|
|
||||||
{
|
|
||||||
struct dijkstra_net_meta *net_meta;
|
|
||||||
GList *l = NULL;
|
|
||||||
|
|
||||||
if (net_type == STREET)
|
|
||||||
name = "all";
|
|
||||||
|
|
||||||
for (l = solver->net_metas; l != NULL; l = l->next) {
|
|
||||||
net_meta = l->data;
|
|
||||||
if (net_meta->type == net_type &&
|
|
||||||
!strcmp(net_meta->name, name) &&
|
|
||||||
(net_meta->access && (!strcmp(net_meta->access, access)))) {
|
|
||||||
return net_meta;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
net_meta = dijkstra_net_meta_new(net_type, name, access);
|
|
||||||
solver->net_metas = g_list_append(solver->net_metas, (void *) net_meta);
|
|
||||||
return net_meta;
|
|
||||||
}
|
|
@ -1,84 +0,0 @@
|
|||||||
#ifndef DIJKSTRAGRAPH_H
|
|
||||||
#define DIJKSTRAGRAPH_H
|
|
||||||
|
|
||||||
#include <glib.h>
|
|
||||||
|
|
||||||
#define DIJKSTRA_WEIGHT_AUTO (-1)
|
|
||||||
|
|
||||||
typedef int dijkstra_cost;
|
|
||||||
#define DIJKSTRA_COST_MAX INT_MAX
|
|
||||||
#define DIJKSTRA_COST_MIN 1.0
|
|
||||||
|
|
||||||
struct dijkstra_solver {
|
|
||||||
GList *nodes;
|
|
||||||
GList *paths;
|
|
||||||
GList *net_metas;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct position {
|
|
||||||
float x;
|
|
||||||
float y;
|
|
||||||
int z;
|
|
||||||
};
|
|
||||||
|
|
||||||
enum dijkstra_net_type {STREET, TRAINLINE, ACCESS};
|
|
||||||
|
|
||||||
struct dijkstra_net_meta {
|
|
||||||
char *name;
|
|
||||||
enum dijkstra_net_type type;
|
|
||||||
char *access;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct dijkstra_path {
|
|
||||||
struct dijkstra_node *source;
|
|
||||||
struct dijkstra_node *destination;
|
|
||||||
char *name;
|
|
||||||
dijkstra_cost weight;
|
|
||||||
struct dijkstra_net_meta *net_meta;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct dijkstra_node {
|
|
||||||
struct position position;
|
|
||||||
struct dijkstra_net_meta *net_meta;
|
|
||||||
|
|
||||||
GList *paths;
|
|
||||||
|
|
||||||
int uid;
|
|
||||||
};
|
|
||||||
|
|
||||||
/* User Functions */
|
|
||||||
struct dijkstra_path *dijkstra_path_new_to_list(struct dijkstra_solver *solver,
|
|
||||||
float x1,
|
|
||||||
float y1,
|
|
||||||
float x2,
|
|
||||||
float y2,
|
|
||||||
int z1,
|
|
||||||
int z2,
|
|
||||||
char *name,
|
|
||||||
struct dijkstra_net_meta *net_meta,
|
|
||||||
dijkstra_cost weight);
|
|
||||||
|
|
||||||
struct dijkstra_solver *dijkstra_solver_new();
|
|
||||||
void dijkstra_solver_free(struct dijkstra_solver *solver);
|
|
||||||
|
|
||||||
/* Library functions */
|
|
||||||
struct dijkstra_node *dijkstra_node_get_connection(struct dijkstra_node *node,
|
|
||||||
struct dijkstra_path *path);
|
|
||||||
struct dijkstra_node *dijkstra_node_find_closest_node(struct dijkstra_solver *solver,
|
|
||||||
struct position *position);
|
|
||||||
void dijkstra_path_intersect_all(struct dijkstra_solver *solver,
|
|
||||||
struct dijkstra_path *path,
|
|
||||||
struct dijkstra_path *path_last);
|
|
||||||
struct dijkstra_net_meta *dijkstra_net_meta_new_to_list(struct dijkstra_solver *solver,
|
|
||||||
enum dijkstra_net_type net_type,
|
|
||||||
char *name,
|
|
||||||
char *access);
|
|
||||||
|
|
||||||
/* For debugging only */
|
|
||||||
struct dijkstra_node *dijkstra_node_new_to_list(GList **list,
|
|
||||||
float x,
|
|
||||||
float y,
|
|
||||||
int z,
|
|
||||||
struct dijkstra_net_meta *net_meta);
|
|
||||||
|
|
||||||
#endif // DIJKSTRAGRAPH_H
|
|
@ -1,397 +0,0 @@
|
|||||||
#include "dijkstrasearch.h"
|
|
||||||
#include "logging.h"
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <math.h>
|
|
||||||
|
|
||||||
void dijkstra_search_reset(struct dijkstra_search *search)
|
|
||||||
{
|
|
||||||
unsigned int i;
|
|
||||||
for (i=0; i < search->node_count; i++) {
|
|
||||||
search->states[i]->cost = DIJKSTRA_COST_MAX;
|
|
||||||
search->states[i]->visited = 0;
|
|
||||||
search->states[i]->cheapest_path = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (search->start) {
|
|
||||||
search->states[search->start->uid]->cost = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (search->active_nodes) {
|
|
||||||
g_list_free(search->active_nodes);
|
|
||||||
search->active_nodes = NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WARNING: After creating this object, you must NOT modify the solver!
|
|
||||||
struct dijkstra_search *dijkstra_search_new(struct dijkstra_solver *solver)
|
|
||||||
{
|
|
||||||
struct dijkstra_search *search;
|
|
||||||
unsigned int i;
|
|
||||||
|
|
||||||
search = malloc(sizeof(*search));
|
|
||||||
if (search) {
|
|
||||||
search->solver = solver;
|
|
||||||
search->states = malloc(sizeof(*search->states) * g_list_length(solver->nodes));
|
|
||||||
search->node_count = g_list_length(solver->nodes);
|
|
||||||
search->start = NULL;
|
|
||||||
for (i=0; i < search->node_count; i++) {
|
|
||||||
search->states[i] = malloc(sizeof(struct dijkstra_state));
|
|
||||||
if (!search->states) {
|
|
||||||
// TODO: I should dealloc all allocated memory before returning,
|
|
||||||
// but whatever, we'll crash and burn one way of the other.
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
search->active_nodes = NULL;
|
|
||||||
dijkstra_search_reset(search);
|
|
||||||
}
|
|
||||||
|
|
||||||
return search;
|
|
||||||
}
|
|
||||||
|
|
||||||
void dijkstra_search_free(struct dijkstra_search *search)
|
|
||||||
{
|
|
||||||
unsigned int i;
|
|
||||||
|
|
||||||
for (i=0; i < search->node_count; i++) {
|
|
||||||
free(search->states[i]);
|
|
||||||
}
|
|
||||||
free(search->states);
|
|
||||||
free(search);
|
|
||||||
}
|
|
||||||
|
|
||||||
void dijkstra_search_set_start(struct dijkstra_search *search,
|
|
||||||
struct dijkstra_node *start)
|
|
||||||
{
|
|
||||||
if (search->start != start) {
|
|
||||||
search->start = start;
|
|
||||||
dijkstra_search_reset(search);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct dijkstra_node *dijkstra_search_process_queue(struct dijkstra_search *search)
|
|
||||||
{
|
|
||||||
struct dijkstra_path *path;
|
|
||||||
struct dijkstra_state *state;
|
|
||||||
struct dijkstra_node *connection;
|
|
||||||
struct dijkstra_node *cheapest_node = NULL;
|
|
||||||
struct dijkstra_node *node;
|
|
||||||
|
|
||||||
dijkstra_cost cost;
|
|
||||||
GList *l;
|
|
||||||
|
|
||||||
// 1. Find cheapest node in active nodes
|
|
||||||
// TODO: We can make this significantly quicker by using a sorted list
|
|
||||||
for (l = search->active_nodes; l != NULL; l = l->next) {
|
|
||||||
node = (struct dijkstra_node*) l->data;
|
|
||||||
state = search->states[node->uid];
|
|
||||||
if (!cheapest_node ||
|
|
||||||
state->cost < search->states[cheapest_node->uid]->cost) {
|
|
||||||
cheapest_node = node;
|
|
||||||
}
|
|
||||||
if (state->visited) {
|
|
||||||
report(LL_WARNING,
|
|
||||||
"Visited node in active list (%d). This is probably an error.",
|
|
||||||
node->uid); // TODO: This can be removed after I'm sure the algorithm works correctly
|
|
||||||
}
|
|
||||||
}
|
|
||||||
node = cheapest_node;
|
|
||||||
state = search->states[node->uid];
|
|
||||||
report(LL_NOISY, "Current cheapest node is %d", node->uid);
|
|
||||||
|
|
||||||
// 2. Mark the current node as visited and remove it from the actives
|
|
||||||
state->visited = 1;
|
|
||||||
search->active_nodes = g_list_remove(search->active_nodes, node);
|
|
||||||
if (!state->cheapest_path && node != search->start) {
|
|
||||||
report(LL_CRITICAL, "DEBUG: marked current node as visited, even though it doesn't have a path to it. Cost is %d.",
|
|
||||||
state->cost);
|
|
||||||
getchar();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Update connecting nodes
|
|
||||||
for (l = node->paths; l != NULL; l = l->next) {
|
|
||||||
path = (struct dijkstra_path*) l->data;
|
|
||||||
cost = state->cost + path->weight;
|
|
||||||
if (state->cheapest_path && path->net_meta->type == ACCESS) { // If we just jumped onto an access path
|
|
||||||
if (state->cheapest_path->net_meta->type != ACCESS) {// and we came from a non-access path (getting on or off)
|
|
||||||
cost += (DIJKSTRA_ACCESS_PENALTY * 100.0); // add a penalty
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
connection = dijkstra_node_get_connection(node, path);
|
|
||||||
|
|
||||||
// 3.1 Update cost if cheaper
|
|
||||||
if (cost < search->states[connection->uid]->cost) {
|
|
||||||
search->states[connection->uid]->cost = cost;
|
|
||||||
search->states[connection->uid]->cheapest_path = path;
|
|
||||||
report(LL_NOISY, " Set cost of node %d to %d (backpath from %d to %d)", connection->uid, cost, connection->uid, node->uid);
|
|
||||||
if (search->states[connection->uid]->visited) {
|
|
||||||
report(LL_CRITICAL, " Reset cost of visited node!"); // TODO: not necessary if algorithm OK
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 3.2. Add connecting nodes to active nodes if not already visited
|
|
||||||
if (!search->states[connection->uid]->visited) {
|
|
||||||
if (!g_list_find(search->active_nodes, connection)) {
|
|
||||||
search->active_nodes = g_list_append(search->active_nodes,
|
|
||||||
(gpointer) connection);
|
|
||||||
report(LL_NOISY, " Append %d as an active node.", connection->uid);
|
|
||||||
if (!search->states[connection->uid]->cheapest_path) {
|
|
||||||
report(LL_CRITICAL, "Appended a node without a path!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
* \brief dijkstra_search_find_path Calculates the path between start and destination
|
|
||||||
* \param search
|
|
||||||
* \param destination the destination. May also be set to NULL to calculate the entire map costs.
|
|
||||||
* \return
|
|
||||||
*/
|
|
||||||
int dijkstra_search_find_path(struct dijkstra_search *search,
|
|
||||||
struct dijkstra_node *destination)
|
|
||||||
{
|
|
||||||
int iteration = 0;
|
|
||||||
search->active_nodes = g_list_append(search->active_nodes, search->start);
|
|
||||||
|
|
||||||
while (g_list_length(search->active_nodes) > 0) {
|
|
||||||
iteration++;
|
|
||||||
dijkstra_search_process_queue(search);
|
|
||||||
if (destination && search->states[destination->uid]->visited)
|
|
||||||
return iteration;
|
|
||||||
if (iteration >= DIJKSTRA_SEARCH_MAX_ITERATIONS) {
|
|
||||||
report(LL_ERROR, "Exceeded maximum iteration limit for query.");
|
|
||||||
return -2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!destination)
|
|
||||||
return iteration;
|
|
||||||
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
* \brief dijkstra_search_get_path get list of nodes (must call dijkstra_search_find_path before this)
|
|
||||||
* \param search
|
|
||||||
* \param destination
|
|
||||||
* \return a list of nodes or NULL if the route has not been found
|
|
||||||
*/
|
|
||||||
GList *dijkstra_search_get_route(struct dijkstra_search *search,
|
|
||||||
struct dijkstra_node *destination)
|
|
||||||
{
|
|
||||||
GList *l = NULL;
|
|
||||||
struct dijkstra_node *node = NULL;
|
|
||||||
unsigned int i = 0;
|
|
||||||
|
|
||||||
if (!destination || !search->states[destination->uid]->visited)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
node = destination;
|
|
||||||
while (1) {
|
|
||||||
l = g_list_prepend(l, node);
|
|
||||||
|
|
||||||
if (node == search->start)
|
|
||||||
break;
|
|
||||||
|
|
||||||
node = dijkstra_node_get_connection(node, search->states[node->uid]->cheapest_path);
|
|
||||||
|
|
||||||
if (i++ > search->node_count) {
|
|
||||||
report(LL_CRITICAL, "Iteration limit for backsolver. This should never happen.");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return l;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
* \brief sdprintf printf to a dynamic buffer
|
|
||||||
* \param buf The character buffer. Will append if not NULL;
|
|
||||||
* \param offset Should be set to strlen when continuing a string. Will be incremented automatically.
|
|
||||||
* \param __format printf format string
|
|
||||||
* \return the number of bytes written
|
|
||||||
*/
|
|
||||||
int sdprintf(char **buf, int *offset, const char *__restrict __format, ...)
|
|
||||||
{
|
|
||||||
va_list vargs;
|
|
||||||
int len;
|
|
||||||
|
|
||||||
va_start(vargs, __format);
|
|
||||||
len = vsnprintf(NULL, 0, __format, vargs);
|
|
||||||
va_end(vargs);
|
|
||||||
*buf = realloc(*buf, *offset + len + 1);
|
|
||||||
va_start(vargs, __format);
|
|
||||||
len = vsprintf(*buf + *offset, __format, vargs);
|
|
||||||
va_end(vargs);
|
|
||||||
|
|
||||||
*offset += len;
|
|
||||||
return len;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Rewrite to use glib-json... On the other hand, this is probably faster.
|
|
||||||
int dijkstra_search_route_to_geojson(struct dijkstra_search *search,
|
|
||||||
GList *route,
|
|
||||||
char **buf)
|
|
||||||
{
|
|
||||||
GList *l = NULL;
|
|
||||||
struct dijkstra_node *node = NULL;
|
|
||||||
struct dijkstra_node *node_last = NULL;
|
|
||||||
struct dijkstra_path *path_to_this = NULL;
|
|
||||||
struct dijkstra_path *path_to_next = NULL;
|
|
||||||
|
|
||||||
float heading = -1.0;
|
|
||||||
float last_heading = 0;
|
|
||||||
float heading_diff;
|
|
||||||
char *relative_direction_str = NULL;
|
|
||||||
char *format_str = NULL;
|
|
||||||
int offset = 0;
|
|
||||||
|
|
||||||
*buf = NULL;
|
|
||||||
|
|
||||||
sdprintf(buf, &offset, "[");
|
|
||||||
|
|
||||||
for (l = route; l != NULL; l = l->next) {
|
|
||||||
node = (struct dijkstra_node*) l->data;
|
|
||||||
if (node_last) {
|
|
||||||
path_to_this = search->states[node_last->uid]->cheapest_path;
|
|
||||||
path_to_next = search->states[node->uid]->cheapest_path;
|
|
||||||
if (!path_to_next) {
|
|
||||||
report(LL_CRITICAL, "INTERNAL ERROR: path_to_next is NULL");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (!path_to_this) {
|
|
||||||
// This happens at the very beginning.
|
|
||||||
path_to_this = path_to_next; // Pretend we were already where we started.
|
|
||||||
}
|
|
||||||
|
|
||||||
heading = atan2f((node->position.y - node_last->position.y), (node->position.x - node_last->position.x)) / (M_PI * 2) * 360;
|
|
||||||
if (heading < 0.0)
|
|
||||||
heading += 360;
|
|
||||||
heading_diff = last_heading - heading;
|
|
||||||
if (fabsf(heading_diff) > 180)
|
|
||||||
heading_diff = heading - last_heading;
|
|
||||||
if (heading_diff > 25) {
|
|
||||||
relative_direction_str = "right";
|
|
||||||
} else if (heading_diff < -25) {
|
|
||||||
relative_direction_str = "left";
|
|
||||||
} else {
|
|
||||||
relative_direction_str = "straight";
|
|
||||||
}
|
|
||||||
|
|
||||||
format_str = "Internal error in format_str in dijkstra_search_route_to_geojson()";
|
|
||||||
switch (path_to_next->net_meta->type) {
|
|
||||||
case STREET:
|
|
||||||
if (node_last == search->start)
|
|
||||||
format_str = "Start on %2$s";
|
|
||||||
else if (path_to_this->name == path_to_next->name)
|
|
||||||
format_str = "Stay on %2$s by going %1$s";
|
|
||||||
else
|
|
||||||
format_str = "Go %s onto %s";
|
|
||||||
break;
|
|
||||||
|
|
||||||
case TRAINLINE:
|
|
||||||
if (path_to_this->net_meta->type == ACCESS) // Enter train
|
|
||||||
format_str = "[T] Get on the %2$s";
|
|
||||||
else // Stay on train
|
|
||||||
format_str = "[T] Stay on the %2$s";
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ACCESS:
|
|
||||||
if (path_to_this->net_meta->type == TRAINLINE) // Enter station from train
|
|
||||||
format_str = "[T] Leave the train at %2$s to your %1$s";
|
|
||||||
else // Enter station from street
|
|
||||||
format_str = "[T] Enter %2$s on your %1$s";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (node_last != search->start)
|
|
||||||
sdprintf(buf, &offset, ",");
|
|
||||||
sdprintf(buf, &offset, "\n");
|
|
||||||
sdprintf(buf, &offset, "{\"type\": \"Feature\",\n"
|
|
||||||
" \"geometry\": {\n"
|
|
||||||
" \"type\": \"LineString\", \"coordinates\": [[%f,%f],[%f,%f]]\n"
|
|
||||||
" },\n",
|
|
||||||
node_last->position.x, node_last->position.y,
|
|
||||||
node->position.x, node->position.y);
|
|
||||||
sdprintf(buf, &offset, " \"properties\": {\n"
|
|
||||||
" \"heading\": \"%f\",\n"
|
|
||||||
" \"through\": \"%s\",\n"
|
|
||||||
" \"cost\": \"%d\",\n"
|
|
||||||
" \"type\": \"%d\",\n",
|
|
||||||
heading, path_to_next->name, search->states[node->uid]->cost, path_to_next->net_meta->type);
|
|
||||||
sdprintf(buf, &offset, " \"description\": \"");
|
|
||||||
sdprintf(buf, &offset, format_str, relative_direction_str, search->states[node->uid]->cheapest_path->name);
|
|
||||||
sdprintf(buf, &offset, "\"\n");
|
|
||||||
|
|
||||||
sdprintf(buf, &offset, " }\n");
|
|
||||||
sdprintf(buf, &offset, "}");
|
|
||||||
last_heading = heading;
|
|
||||||
}
|
|
||||||
node_last = node;
|
|
||||||
}
|
|
||||||
|
|
||||||
sdprintf(buf, &offset, "\n]\n");
|
|
||||||
|
|
||||||
return offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
void dijkstra_print_path(struct dijkstra_search *search,
|
|
||||||
struct dijkstra_node *destination)
|
|
||||||
{
|
|
||||||
struct dijkstra_node *node = NULL;
|
|
||||||
unsigned int i = 0;
|
|
||||||
|
|
||||||
if (!destination || !search->states[destination->uid]->visited)
|
|
||||||
return;
|
|
||||||
|
|
||||||
node = destination;
|
|
||||||
while (1) {
|
|
||||||
if (node != destination)
|
|
||||||
printf(",");
|
|
||||||
printf("[%d,%d]", (int) node->position.x, (int) node->position.y);
|
|
||||||
|
|
||||||
if (node == search->start)
|
|
||||||
break;
|
|
||||||
node = dijkstra_node_get_connection(node, search->states[node->uid]->cheapest_path);
|
|
||||||
if (i++ > search->node_count) {
|
|
||||||
report(LL_CRITICAL, "Iteration limit for backsolver. This should never happen.");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
printf("\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
void dijkstra_print_nodes(struct dijkstra_search *search)
|
|
||||||
{
|
|
||||||
GList *l;
|
|
||||||
struct dijkstra_node *node;
|
|
||||||
|
|
||||||
printf("[");
|
|
||||||
for (l = search->solver->nodes; l != NULL; l = l->next) {
|
|
||||||
node = (struct dijkstra_node*) l->data;
|
|
||||||
printf("{ \"type\": \"Feature\",\n"
|
|
||||||
" \"geometry\" : {\n"
|
|
||||||
" \"type\" : \"Point\", \"coordinates\" : [%f, %f]\n"
|
|
||||||
" },\n"
|
|
||||||
" \"properties\" : {\n", node->position.x, node->position.y);
|
|
||||||
if (search->states[node->uid]->cheapest_path) {
|
|
||||||
printf(" \"name\" : \"Node %d (cost %d, from %d)\"\n",
|
|
||||||
node->uid, search->states[node->uid]->cost, dijkstra_node_get_connection(node, search->states[node->uid]->cheapest_path)->uid);
|
|
||||||
} else if (node == search->start) {
|
|
||||||
printf(" \"name\" : \"Starting node %d\"\n",
|
|
||||||
node->uid);
|
|
||||||
} else {
|
|
||||||
printf(" \"name\" : \"Unvisited node %d\"\n",
|
|
||||||
node->uid);
|
|
||||||
}
|
|
||||||
printf(" }\n"
|
|
||||||
"},\n");
|
|
||||||
|
|
||||||
}
|
|
||||||
printf("{}]\n");
|
|
||||||
}
|
|
@ -1,40 +0,0 @@
|
|||||||
#ifndef DIJKSTRASEARCH_H
|
|
||||||
#define DIJKSTRASEARCH_H
|
|
||||||
|
|
||||||
#include "dijkstragraph.h"
|
|
||||||
|
|
||||||
#define DIJKSTRA_SEARCH_MAX_ITERATIONS 5000
|
|
||||||
|
|
||||||
#define DIJKSTRA_ACCESS_PENALTY 300.0
|
|
||||||
|
|
||||||
struct dijkstra_state {
|
|
||||||
struct dijkstra_path *cheapest_path;
|
|
||||||
dijkstra_cost cost;
|
|
||||||
int visited;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct dijkstra_search {
|
|
||||||
struct dijkstra_solver *solver;
|
|
||||||
struct dijkstra_state **states;
|
|
||||||
unsigned int node_count;
|
|
||||||
struct dijkstra_node *start;
|
|
||||||
GList *active_nodes;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct dijkstra_search *dijkstra_search_new(struct dijkstra_solver *solver);
|
|
||||||
void dijkstra_search_free(struct dijkstra_search *search);
|
|
||||||
void dijkstra_search_set_start(struct dijkstra_search *search,
|
|
||||||
struct dijkstra_node *start);
|
|
||||||
int dijkstra_search_find_path(struct dijkstra_search *search,
|
|
||||||
struct dijkstra_node *destination);
|
|
||||||
GList *dijkstra_search_get_route(struct dijkstra_search *search,
|
|
||||||
struct dijkstra_node *destination);
|
|
||||||
|
|
||||||
/* Debugging functions */
|
|
||||||
void dijkstra_print_path(struct dijkstra_search *search,
|
|
||||||
struct dijkstra_node *destination);
|
|
||||||
void dijkstra_print_nodes(struct dijkstra_search *search);
|
|
||||||
int dijkstra_search_route_to_geojson(struct dijkstra_search *search,
|
|
||||||
GList *route,
|
|
||||||
char **buf);
|
|
||||||
#endif // DIJKSTRASEARCH_H
|
|
@ -1,171 +0,0 @@
|
|||||||
#include "dijkstraserv.h"
|
|
||||||
|
|
||||||
static int dijkstra_serv_perform_search(struct dijkstra_serv *serv,
|
|
||||||
struct position *pos_start,
|
|
||||||
struct position *pos_destination,
|
|
||||||
char **result) {
|
|
||||||
struct dijkstra_node *start = NULL;
|
|
||||||
struct dijkstra_node *destination = NULL;
|
|
||||||
|
|
||||||
struct dijkstra_search *search;
|
|
||||||
int iterations;
|
|
||||||
GList *route;
|
|
||||||
|
|
||||||
report(LL_INFO, "Requested route from %f,%f to %f,%f.",
|
|
||||||
pos_start->x, pos_start->y,
|
|
||||||
pos_destination->x, pos_destination->y);
|
|
||||||
|
|
||||||
/* Find closest node */ // TODO: This should be closest point on path, but for now this'll do.
|
|
||||||
start = dijkstra_node_find_closest_node(serv->solver, pos_start);
|
|
||||||
if (!start)
|
|
||||||
return 0;
|
|
||||||
destination = dijkstra_node_find_closest_node(serv->solver, pos_destination);
|
|
||||||
if (!destination)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
report(LL_DEBUG, "Searching route from %f,%f to %f,%f.",
|
|
||||||
start->position.x, start->position.y,
|
|
||||||
destination->position.x, destination->position.y);
|
|
||||||
|
|
||||||
search = dijkstra_search_new(serv->solver);
|
|
||||||
if (!search) {
|
|
||||||
report(LL_CRITICAL, "Error allocating dijkstra_search");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
dijkstra_search_set_start(search, start);
|
|
||||||
if ((iterations = dijkstra_search_find_path(search, destination)) > 0) {
|
|
||||||
report(LL_INFO, " Found path from %d to %d after %d iterations.", start->uid, destination->uid, iterations);
|
|
||||||
route = dijkstra_search_get_route(search, destination);
|
|
||||||
return dijkstra_search_route_to_geojson(search, route, result);
|
|
||||||
} else {
|
|
||||||
report(LL_INFO, " Couldn't find path.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void dijkstra_serv_soup_callback (SoupServer *server,
|
|
||||||
SoupMessage *msg,
|
|
||||||
const char *path,
|
|
||||||
GHashTable *query,
|
|
||||||
SoupClientContext *client,
|
|
||||||
gpointer user_data)
|
|
||||||
{
|
|
||||||
struct dijkstra_serv *serv = (struct dijkstra_serv *) user_data;
|
|
||||||
GError *err = NULL;
|
|
||||||
GMatchInfo *matchInfo;
|
|
||||||
GRegex *regex;
|
|
||||||
char *body;
|
|
||||||
int body_len;
|
|
||||||
int i = 0;
|
|
||||||
struct position pos_start, pos_destination;
|
|
||||||
|
|
||||||
report(LL_INFO, "New request: %s", path);
|
|
||||||
if (msg->method != SOUP_METHOD_GET) {
|
|
||||||
soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (strlen(path) > 40) {
|
|
||||||
soup_message_set_status (msg, SOUP_STATUS_BAD_REQUEST);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
regex = g_regex_new("([\\-0-9]+)", 0, 0, &err);
|
|
||||||
g_regex_match (regex, path, 0, &matchInfo);
|
|
||||||
|
|
||||||
while (g_match_info_matches(matchInfo)) {
|
|
||||||
float result_num;
|
|
||||||
gchar *result;
|
|
||||||
result = g_match_info_fetch(matchInfo, 0);
|
|
||||||
if (!result)
|
|
||||||
break;
|
|
||||||
|
|
||||||
result_num = atof(result);
|
|
||||||
switch (i) {
|
|
||||||
case 0:
|
|
||||||
pos_start.x = result_num;
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
pos_start.y = result_num;
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
pos_destination.x = result_num;
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
pos_destination.y = result_num;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
g_match_info_next(matchInfo, &err);
|
|
||||||
g_free(result);
|
|
||||||
|
|
||||||
i++;
|
|
||||||
if (i >= 4)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
g_regex_unref(regex);
|
|
||||||
|
|
||||||
if (i == 4) {
|
|
||||||
body_len = dijkstra_serv_perform_search(serv, &pos_start, &pos_destination, &body);
|
|
||||||
if (!body_len) {
|
|
||||||
body = strdup("Not found.");
|
|
||||||
body_len = 9;
|
|
||||||
soup_message_set_status (msg, SOUP_STATUS_NOT_FOUND);
|
|
||||||
} else {
|
|
||||||
soup_message_set_status (msg, SOUP_STATUS_OK);
|
|
||||||
}
|
|
||||||
|
|
||||||
soup_message_set_response (msg, "application/json", SOUP_MEMORY_COPY,
|
|
||||||
body, body_len);
|
|
||||||
free(body);
|
|
||||||
} else {
|
|
||||||
report(LL_WARNING, " Invalid request.");
|
|
||||||
soup_message_set_status (msg, SOUP_STATUS_MALFORMED);
|
|
||||||
soup_message_set_response (msg, "text/plain", SOUP_MEMORY_COPY,
|
|
||||||
"Invalid request", 15);
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct dijkstra_serv *dijkstra_serv_new(
|
|
||||||
struct dijkstra_solver *solver,
|
|
||||||
int port)
|
|
||||||
{
|
|
||||||
struct dijkstra_serv *serv;
|
|
||||||
|
|
||||||
serv = (struct dijkstra_serv*) malloc(sizeof(*serv));
|
|
||||||
if (serv) {
|
|
||||||
serv->solver = solver;
|
|
||||||
serv->port = port;
|
|
||||||
serv->soupserver = soup_server_new(NULL, NULL);
|
|
||||||
if (!serv->soupserver) {
|
|
||||||
free(serv);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
soup_server_add_handler (serv->soupserver,
|
|
||||||
"/", dijkstra_serv_soup_callback,
|
|
||||||
serv, NULL); // TODO: The last arg here should probably not be NULL...
|
|
||||||
}
|
|
||||||
|
|
||||||
return serv;
|
|
||||||
}
|
|
||||||
|
|
||||||
int dijkstra_serv_run(struct dijkstra_serv *serv)
|
|
||||||
{
|
|
||||||
GError *err = NULL;
|
|
||||||
|
|
||||||
if (!soup_server_listen_all(serv->soupserver, serv->port, 0, &err)) {
|
|
||||||
report(LL_CRITICAL, "Couldn't start server: %s", err->message);
|
|
||||||
g_error_free(err);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
report(LL_INFO, "Listening on port %d.", serv->port);
|
|
||||||
|
|
||||||
GMainLoop *loop;
|
|
||||||
loop = g_main_loop_new(NULL, TRUE);
|
|
||||||
g_main_loop_run(loop); // TODO: Add way to gracefully exit loop
|
|
||||||
return 0;
|
|
||||||
}
|
|
@ -1,22 +0,0 @@
|
|||||||
#ifndef DIJKSTRASERV_H
|
|
||||||
#define DIJKSTRASERV_H
|
|
||||||
|
|
||||||
#include "dijkstragraph.h"
|
|
||||||
#include "dijkstrasearch.h"
|
|
||||||
#include "logging.h"
|
|
||||||
|
|
||||||
#include <libsoup/soup.h>
|
|
||||||
|
|
||||||
struct dijkstra_serv {
|
|
||||||
struct dijkstra_solver *solver;
|
|
||||||
int port;
|
|
||||||
|
|
||||||
SoupServer *soupserver;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct dijkstra_serv *dijkstra_serv_new(
|
|
||||||
struct dijkstra_solver *solver,
|
|
||||||
int port);
|
|
||||||
int dijkstra_serv_run(struct dijkstra_serv *serv);
|
|
||||||
|
|
||||||
#endif // DIJKSTRASERV_H
|
|
172
src/geojson.c
172
src/geojson.c
@ -1,172 +0,0 @@
|
|||||||
#include "geojson.h"
|
|
||||||
#include "dijkstragraph.h"
|
|
||||||
#include "logging.h"
|
|
||||||
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <glib-object.h>
|
|
||||||
#include <json-glib/json-glib.h>
|
|
||||||
|
|
||||||
int add_geojson_to_dijkstra(struct dijkstra_solver *solver,
|
|
||||||
char *filename,
|
|
||||||
char *type)
|
|
||||||
{
|
|
||||||
JsonParser *parser;
|
|
||||||
JsonNode *root;
|
|
||||||
JsonArray *arr;
|
|
||||||
JsonNode *node;
|
|
||||||
JsonReader *reader;
|
|
||||||
GError *error;
|
|
||||||
|
|
||||||
int i, j;
|
|
||||||
int i_max, j_max;
|
|
||||||
|
|
||||||
char *name = NULL;
|
|
||||||
char *access = NULL;
|
|
||||||
int x, y, z;
|
|
||||||
int x_last, y_last, z_last;
|
|
||||||
|
|
||||||
enum dijkstra_net_type net_type;
|
|
||||||
struct dijkstra_net_meta *net_meta;
|
|
||||||
|
|
||||||
struct _stats {
|
|
||||||
int nodes;
|
|
||||||
int paths;
|
|
||||||
} stats = {.nodes = 0, .paths = 0};
|
|
||||||
|
|
||||||
report(LL_INFO, "Loading GeoJson '%s' data from %s...", type, filename);
|
|
||||||
|
|
||||||
if (!strcmp("street", type))
|
|
||||||
net_type = STREET;
|
|
||||||
else if (!strcmp("trainline", type))
|
|
||||||
net_type = TRAINLINE;
|
|
||||||
else if (!strcmp("access", type))
|
|
||||||
net_type = ACCESS;
|
|
||||||
else {
|
|
||||||
report(LL_ERROR, "Type '%s' not supported.\n", type);
|
|
||||||
return EXIT_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
parser = json_parser_new ();
|
|
||||||
|
|
||||||
error = NULL;
|
|
||||||
json_parser_load_from_file(parser, filename, &error);
|
|
||||||
if (error)
|
|
||||||
{
|
|
||||||
report(LL_ERROR, "Unable to parse `%s': %s\n", filename, error->message);
|
|
||||||
g_error_free(error);
|
|
||||||
g_object_unref(parser);
|
|
||||||
return EXIT_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
root = json_parser_get_root(parser);
|
|
||||||
|
|
||||||
arr = json_node_get_array(root);
|
|
||||||
if (arr) {
|
|
||||||
i_max = json_array_get_length(arr);
|
|
||||||
net_meta = NULL;
|
|
||||||
for (i = 0; i < i_max; i++)
|
|
||||||
{
|
|
||||||
node = json_array_get_element(arr, i);
|
|
||||||
if (node) {
|
|
||||||
reader = json_reader_new(node);
|
|
||||||
|
|
||||||
json_reader_read_member(reader, "properties");
|
|
||||||
if (json_reader_get_error(reader)) {
|
|
||||||
g_object_unref(reader);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
json_reader_read_member(reader, "name");
|
|
||||||
name = strdup(json_reader_get_string_value(reader));
|
|
||||||
json_reader_end_member(reader); // name
|
|
||||||
json_reader_end_member(reader); // properties
|
|
||||||
|
|
||||||
switch (net_type) {
|
|
||||||
case STREET:
|
|
||||||
if (!net_meta)
|
|
||||||
net_meta = dijkstra_net_meta_new_to_list(solver, net_type, NULL, NULL);
|
|
||||||
break;
|
|
||||||
case ACCESS:
|
|
||||||
access = name;
|
|
||||||
name = strchr(name, ':');
|
|
||||||
if (name == NULL) {
|
|
||||||
report(LL_ERROR,
|
|
||||||
"Invalid name for accesss. '%s'",
|
|
||||||
access);
|
|
||||||
g_object_unref(reader);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
*name = '\0';
|
|
||||||
do
|
|
||||||
name++;
|
|
||||||
while (*name == ' ' && *name != '\0');
|
|
||||||
G_GNUC_FALLTHROUGH;
|
|
||||||
case TRAINLINE:
|
|
||||||
net_meta = dijkstra_net_meta_new_to_list(solver, net_type, name, access);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
json_reader_read_member(reader, "geometry");
|
|
||||||
json_reader_read_member(reader, "coordinates");
|
|
||||||
z_last = 0; // By default, let's assume height 0
|
|
||||||
if (json_reader_is_array(reader)) {
|
|
||||||
j_max = json_reader_count_elements(reader);
|
|
||||||
for (j = 0; j < j_max; j++) {
|
|
||||||
json_reader_read_element(reader, j);
|
|
||||||
if (json_reader_is_array(reader)) {
|
|
||||||
stats.nodes++;
|
|
||||||
json_reader_read_element(reader, 0);
|
|
||||||
x = json_reader_get_int_value(reader);
|
|
||||||
json_reader_end_element(reader);
|
|
||||||
json_reader_read_element(reader, 1);
|
|
||||||
y = json_reader_get_int_value(reader);
|
|
||||||
json_reader_end_element(reader);
|
|
||||||
if (json_reader_count_elements(reader) > 2) {
|
|
||||||
json_reader_read_element(reader, 2);
|
|
||||||
z = json_reader_get_int_value(reader);
|
|
||||||
json_reader_end_element(reader);
|
|
||||||
} else {
|
|
||||||
z = z_last;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (j > 0) {
|
|
||||||
stats.paths++;
|
|
||||||
dijkstra_path_new_to_list(solver,
|
|
||||||
x_last, y_last,
|
|
||||||
x, y,
|
|
||||||
z, z_last,
|
|
||||||
name,
|
|
||||||
net_meta,
|
|
||||||
DIJKSTRA_WEIGHT_AUTO);
|
|
||||||
}
|
|
||||||
|
|
||||||
x_last = x;
|
|
||||||
y_last = y;
|
|
||||||
z_last = z;
|
|
||||||
} else {
|
|
||||||
report(LL_WARNING, "err 1\n");
|
|
||||||
}
|
|
||||||
json_reader_end_element(reader);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
report(LL_WARNING, "err 0\n");
|
|
||||||
}
|
|
||||||
json_reader_end_member(reader); // coordinates
|
|
||||||
json_reader_end_member(reader); // geometry
|
|
||||||
|
|
||||||
g_object_unref(reader);
|
|
||||||
} else {
|
|
||||||
report(LL_ERROR, "Invalid geojson file.\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
report(LL_ERROR, "Invalid geojson file.\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
report(LL_INFO, "Imported %d nodes and %d paths from GeoJson.",
|
|
||||||
stats.nodes,
|
|
||||||
stats.paths);
|
|
||||||
|
|
||||||
g_object_unref (parser);
|
|
||||||
|
|
||||||
return EXIT_SUCCESS;
|
|
||||||
}
|
|
@ -1,11 +0,0 @@
|
|||||||
#ifndef GEOJSON_H
|
|
||||||
#define GEOJSON_H
|
|
||||||
|
|
||||||
#include <glib.h>
|
|
||||||
#include "dijkstragraph.h"
|
|
||||||
|
|
||||||
int add_geojson_to_dijkstra(struct dijkstra_solver *solver,
|
|
||||||
char *filename,
|
|
||||||
char *type);
|
|
||||||
|
|
||||||
#endif // GEOJSON_H
|
|
@ -1,60 +0,0 @@
|
|||||||
/* ----------------------------------------------------------------------------
|
|
||||||
* C-Report
|
|
||||||
* ----------------------------------------------------------------------------
|
|
||||||
* Author : Markus Koch <markus@notsyncing.net>
|
|
||||||
* Contributors : None
|
|
||||||
* License : Mozilla Public License (MPL) Version 2
|
|
||||||
* ----------------------------------------------------------------------------
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "logging.h"
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdarg.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
static enum log_level current_log_level = LL_INFO;
|
|
||||||
|
|
||||||
static const char *ll_string[] = {
|
|
||||||
"\x1B[31m\x1B[1m[X] ",
|
|
||||||
"\x1B[31m[E] ",
|
|
||||||
"\x1B[33m[W] ",
|
|
||||||
"\x1B[32m[I] ",
|
|
||||||
"\x1B[34m[D] ",
|
|
||||||
"\x1B[34m[d] ",
|
|
||||||
"[?] "
|
|
||||||
};
|
|
||||||
|
|
||||||
int should_report(enum log_level log_level)
|
|
||||||
{
|
|
||||||
return (log_level <= current_log_level);
|
|
||||||
}
|
|
||||||
|
|
||||||
enum log_level report(enum log_level log_level, const char *format, ...)
|
|
||||||
{
|
|
||||||
va_list vargs;
|
|
||||||
|
|
||||||
if (log_level > LL_COUNT)
|
|
||||||
log_level = LL_COUNT;
|
|
||||||
|
|
||||||
if (should_report(log_level)) {
|
|
||||||
va_start(vargs, format);
|
|
||||||
fprintf(stderr, "%s", ll_string[log_level]);
|
|
||||||
vfprintf(stderr, format, vargs);
|
|
||||||
fprintf(stderr, "\x1B[0m\n");
|
|
||||||
va_end(vargs);
|
|
||||||
}
|
|
||||||
|
|
||||||
return log_level;
|
|
||||||
}
|
|
||||||
|
|
||||||
void set_log_level(enum log_level log_level)
|
|
||||||
{
|
|
||||||
if (log_level >= LL_COUNT)
|
|
||||||
log_level = LL_COUNT - 1;
|
|
||||||
current_log_level = log_level;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum log_level get_log_level()
|
|
||||||
{
|
|
||||||
return current_log_level;
|
|
||||||
}
|
|
@ -1,28 +0,0 @@
|
|||||||
/* ----------------------------------------------------------------------------
|
|
||||||
* C-Report
|
|
||||||
* ----------------------------------------------------------------------------
|
|
||||||
* Author : Markus Koch <markus@notsyncing.net>
|
|
||||||
* Contributors : None
|
|
||||||
* License : Mozilla Public License (MPL) Version 2
|
|
||||||
* ----------------------------------------------------------------------------
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
#ifndef LOGGING_H
|
|
||||||
#define LOGGING_H
|
|
||||||
|
|
||||||
#include <errno.h>
|
|
||||||
|
|
||||||
enum log_level {LL_CRITICAL = 0,
|
|
||||||
LL_ERROR,
|
|
||||||
LL_WARNING,
|
|
||||||
LL_INFO,
|
|
||||||
LL_DEBUG,
|
|
||||||
LL_NOISY,
|
|
||||||
LL_COUNT};
|
|
||||||
int should_report(enum log_level log_level);
|
|
||||||
enum log_level report(enum log_level log_level, const char *format, ...);
|
|
||||||
void set_log_level(enum log_level log_level);
|
|
||||||
enum log_level get_log_level();
|
|
||||||
|
|
||||||
#endif // LOGGING_H
|
|
129
src/main.c
129
src/main.c
@ -1,129 +0,0 @@
|
|||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <getopt.h>
|
|
||||||
#include <glib.h>
|
|
||||||
|
|
||||||
#include "dijkstragraph.h"
|
|
||||||
#include "dijkstrasearch.h"
|
|
||||||
#include "dijkstraserv.h"
|
|
||||||
#include "geojson.h"
|
|
||||||
#include "logging.h"
|
|
||||||
|
|
||||||
struct dijkstra_node *start;
|
|
||||||
struct dijkstra_node *dest;
|
|
||||||
|
|
||||||
void help(const char *exec)
|
|
||||||
{
|
|
||||||
fprintf(stderr,
|
|
||||||
"Usage: %s [OPTION] [TYPE:FILE]...\n"
|
|
||||||
"Start the lifo-dijkstraserv routing server.\n\n"
|
|
||||||
"Available options:\n"
|
|
||||||
" -s\tset output to silent\n"
|
|
||||||
" -v\tincrease verbosity. Maybe used multiple times.\n"
|
|
||||||
" -p\tport number (default 6802)\n\n"
|
|
||||||
"TYPE can be any of:\n"
|
|
||||||
" street (default), trainline, access\n\n"
|
|
||||||
"At least one GeoJson FILE must be presented.\n",
|
|
||||||
exec);
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char *argv[])
|
|
||||||
{
|
|
||||||
int opt;
|
|
||||||
int i;
|
|
||||||
|
|
||||||
struct dijkstra_solver *solver;
|
|
||||||
|
|
||||||
GList *l = NULL;
|
|
||||||
struct dijkstra_node *node;
|
|
||||||
struct dijkstra_path *path;
|
|
||||||
char *file_name, *file_type;
|
|
||||||
|
|
||||||
int port = 6802;
|
|
||||||
struct dijkstra_serv *serv;
|
|
||||||
|
|
||||||
/* Init stuff */
|
|
||||||
set_log_level(LL_DEBUG);
|
|
||||||
|
|
||||||
/* Process arguments */
|
|
||||||
while((opt = getopt(argc, argv, "vsp")) != -1) {
|
|
||||||
switch(opt)
|
|
||||||
{
|
|
||||||
case 'v':
|
|
||||||
set_log_level(get_log_level() + 1);
|
|
||||||
break;
|
|
||||||
case 's':
|
|
||||||
set_log_level(0);
|
|
||||||
break;
|
|
||||||
case 'p':
|
|
||||||
port = atoi(optarg);
|
|
||||||
break;
|
|
||||||
case ':':
|
|
||||||
case '?':
|
|
||||||
help(argv[0]);
|
|
||||||
return 1;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (optind == argc) {
|
|
||||||
help(argv[0]);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Generate solver */
|
|
||||||
solver = dijkstra_solver_new();
|
|
||||||
if (!solver) {
|
|
||||||
report(LL_CRITICAL, "Memory allocation failure.");
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Load map data -> Generate graph */
|
|
||||||
for (i = optind; i < argc; ++i) {
|
|
||||||
file_name = strchr(argv[i], ':');
|
|
||||||
if (!file_name) {
|
|
||||||
file_name = argv[i];
|
|
||||||
file_type = "street";
|
|
||||||
} else {
|
|
||||||
*file_name = '\0';
|
|
||||||
file_name++;
|
|
||||||
file_type = argv[i];
|
|
||||||
}
|
|
||||||
add_geojson_to_dijkstra(solver, file_name, file_type);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (should_report(LL_NOISY)) {
|
|
||||||
for (l = solver->nodes; l != NULL; l = l->next) {
|
|
||||||
node = (struct dijkstra_node*) l->data;
|
|
||||||
fprintf(stderr, "Node %5d @%p: %f,\t%f [%d paths]\n", node->uid, node, node->position.x, node->position.y, g_list_length(node->paths));
|
|
||||||
}
|
|
||||||
printf("\n");
|
|
||||||
|
|
||||||
for (l = solver->paths; l != NULL; l = l->next) {
|
|
||||||
path = (struct dijkstra_path*) l->data;
|
|
||||||
fprintf(stderr, "Path %p: %5d (%f,%f) -> %5d (%f,%f) cost %d \t\"%s\"\n", path,
|
|
||||||
path->source->uid, path->source->position.x, path->source->position.y,
|
|
||||||
path->destination->uid, path->destination->position.x, path->destination->position.y,
|
|
||||||
path->weight,
|
|
||||||
path->name);
|
|
||||||
}
|
|
||||||
printf("\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
report(LL_INFO, "Created graph with %d nodes and %d paths.",
|
|
||||||
g_list_length(solver->nodes),
|
|
||||||
g_list_length(solver->paths));
|
|
||||||
|
|
||||||
/* Start server */
|
|
||||||
serv = dijkstra_serv_new(solver, port);
|
|
||||||
if (!serv) {
|
|
||||||
dijkstra_solver_free(solver);
|
|
||||||
return -4;
|
|
||||||
}
|
|
||||||
dijkstra_serv_run(serv);
|
|
||||||
|
|
||||||
dijkstra_solver_free(solver);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user