kopia lustrzana https://github.com/FacilMap/facilmap
Status commit
rodzic
e857d6872c
commit
282d3afd8f
|
@ -351,7 +351,7 @@ Search for markers and lines inside the map.
|
|||
* `data` (object): An object with the following properties:
|
||||
* `query` (string): The query string
|
||||
* _returns_ (Promise<Array<[Marker](#marker-1)|[Line](#line-1)>>) An array of (stripped down) marker and line objects.
|
||||
The objects only contain the `id`, `name`, `typeId`, ``lat`/`lon` (for markers), `left`/`top`/`right`/`bottom` (for
|
||||
The objects only contain the `id`, `name`, `typeId`, `lat`/`lon` (for markers), `left`/`top`/`right`/`bottom` (for
|
||||
lines) properties, plus an additional `kind` property that is either `"marker"` or `"line"`.
|
||||
|
||||
### `getRoute(data)`
|
||||
|
|
|
@ -7,24 +7,26 @@ import {
|
|||
TrackPoint, Type, TypeCreate, TypeUpdate, View, ViewCreate, ViewUpdate, Writable
|
||||
} from "facilmap-types";
|
||||
|
||||
declare module "facilmap-types/src/events" {
|
||||
interface MapEvents {
|
||||
connect: [];
|
||||
disconnect: [string];
|
||||
connect_error: [Error];
|
||||
export interface SocketEvents extends MapEvents {
|
||||
connect: [];
|
||||
disconnect: [string];
|
||||
connect_error: [Error];
|
||||
|
||||
error: [Error];
|
||||
reconnect: [number];
|
||||
reconnect_attempt: [number];
|
||||
reconnect_error: [Error];
|
||||
reconnect_failed: [];
|
||||
error: [Error];
|
||||
reconnect: [number];
|
||||
reconnect_attempt: [number];
|
||||
reconnect_error: [Error];
|
||||
reconnect_failed: [];
|
||||
|
||||
loadStart: [],
|
||||
loadEnd: []
|
||||
}
|
||||
loadStart: [],
|
||||
loadEnd: [],
|
||||
|
||||
route: [RouteWithTrackPoints | undefined];
|
||||
|
||||
emit: { [eventName in RequestName]: [eventName, RequestData<eventName>] }[RequestName]
|
||||
}
|
||||
|
||||
const MANAGER_EVENTS: Array<EventName<MapEvents>> = ['error', 'reconnect', 'reconnect_attempt', 'reconnect_error', 'reconnect_failed'];
|
||||
const MANAGER_EVENTS: Array<EventName<SocketEvents>> = ['error', 'reconnect', 'reconnect_attempt', 'reconnect_error', 'reconnect_failed'];
|
||||
|
||||
export interface TrackPoints {
|
||||
[idx: number]: TrackPoint;
|
||||
|
@ -58,7 +60,7 @@ export default class Socket {
|
|||
serverError: Error | undefined = undefined;
|
||||
|
||||
_listeners: {
|
||||
[E in EventName<MapEvents>]?: Array<EventHandler<MapEvents, E>>
|
||||
[E in EventName<SocketEvents>]?: Array<EventHandler<SocketEvents, E>>
|
||||
} = { };
|
||||
_listeningToHistory: boolean = false;
|
||||
|
||||
|
@ -75,8 +77,8 @@ export default class Socket {
|
|||
const manager = new Manager(server, { forceNew: true });
|
||||
this.socket = manager.socket("/");
|
||||
|
||||
for(let i of Object.keys(this._handlers) as EventName<MapEvents>[]) {
|
||||
this.on(i, this._handlers[i] as EventHandler<MapEvents, typeof i>);
|
||||
for(let i of Object.keys(this._handlers) as EventName<SocketEvents>[]) {
|
||||
this.on(i, this._handlers[i] as EventHandler<SocketEvents, typeof i>);
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
|
@ -87,27 +89,27 @@ export default class Socket {
|
|||
});
|
||||
}
|
||||
|
||||
on<E extends EventName<MapEvents>>(eventName: E, fn: EventHandler<MapEvents, E>) {
|
||||
let listeners = this._listeners[eventName] as Array<EventHandler<MapEvents, E>> | undefined;
|
||||
on<E extends EventName<SocketEvents>>(eventName: E, fn: EventHandler<SocketEvents, E>) {
|
||||
let listeners = this._listeners[eventName] as Array<EventHandler<SocketEvents, E>> | undefined;
|
||||
if(!listeners) {
|
||||
listeners = this._listeners[eventName] = [ ];
|
||||
(MANAGER_EVENTS.includes(eventName) ? this.socket.io : this.socket)
|
||||
.on(eventName, (...[data]: MapEvents[E]) => { this._simulateEvent(eventName as any, data); });
|
||||
.on(eventName, (...[data]: SocketEvents[E]) => { this._simulateEvent(eventName as any, data); });
|
||||
}
|
||||
|
||||
listeners.push(fn);
|
||||
}
|
||||
|
||||
once<E extends EventName<MapEvents>>(eventName: E, fn: EventHandler<MapEvents, E>) {
|
||||
once<E extends EventName<SocketEvents>>(eventName: E, fn: EventHandler<SocketEvents, E>) {
|
||||
let handler = ((data: any) => {
|
||||
this.removeListener(eventName, handler);
|
||||
(fn as any)(data);
|
||||
}) as EventHandler<MapEvents, E>;
|
||||
}) as EventHandler<SocketEvents, E>;
|
||||
this.on(eventName, handler);
|
||||
}
|
||||
|
||||
removeListener<E extends EventName<MapEvents>>(eventName: E, fn: EventHandler<MapEvents, E>) {
|
||||
const listeners = this._listeners[eventName] as Array<EventHandler<MapEvents, E>> | undefined;
|
||||
removeListener<E extends EventName<SocketEvents>>(eventName: E, fn: EventHandler<SocketEvents, E>) {
|
||||
const listeners = this._listeners[eventName] as Array<EventHandler<SocketEvents, E>> | undefined;
|
||||
if(listeners) {
|
||||
this._listeners[eventName] = listeners.filter((listener) => (listener !== fn)) as any;
|
||||
}
|
||||
|
@ -131,7 +133,7 @@ export default class Socket {
|
|||
}
|
||||
|
||||
_handlers: {
|
||||
[E in EventName<MapEvents>]?: EventHandler<MapEvents, E>
|
||||
[E in EventName<SocketEvents>]?: EventHandler<SocketEvents, E>
|
||||
} = {
|
||||
padData: (data) => {
|
||||
this.padData = data;
|
||||
|
@ -345,6 +347,8 @@ export default class Socket {
|
|||
...route,
|
||||
trackPoints: this._mergeTrackPoints({}, route.trackPoints)
|
||||
};
|
||||
|
||||
this._simulateEvent("route", this.route);
|
||||
}
|
||||
|
||||
return this.route;
|
||||
|
@ -353,6 +357,7 @@ export default class Socket {
|
|||
|
||||
clearRoute() {
|
||||
this.route = undefined;
|
||||
this._simulateEvent("route", undefined);
|
||||
return this._emit("clearRoute");
|
||||
}
|
||||
|
||||
|
@ -363,6 +368,8 @@ export default class Socket {
|
|||
trackPoints: this._mergeTrackPoints({}, route.trackPoints)
|
||||
};
|
||||
|
||||
this._simulateEvent("route", this.route);
|
||||
|
||||
return this.route;
|
||||
});
|
||||
}
|
||||
|
@ -416,17 +423,17 @@ export default class Socket {
|
|||
});
|
||||
}
|
||||
|
||||
_receiveMultiple(obj?: MultipleEvents<MapEvents>) {
|
||||
_receiveMultiple(obj?: MultipleEvents<SocketEvents>) {
|
||||
if (obj) {
|
||||
for(const i of Object.keys(obj) as EventName<MapEvents>[])
|
||||
(obj[i] as Array<MapEvents[typeof i][0]>).forEach((it) => { this._simulateEvent(i, it as any); });
|
||||
for(const i of Object.keys(obj) as EventName<SocketEvents>[])
|
||||
(obj[i] as Array<SocketEvents[typeof i][0]>).forEach((it) => { this._simulateEvent(i, it as any); });
|
||||
}
|
||||
}
|
||||
|
||||
_simulateEvent<E extends EventName<MapEvents>>(eventName: E, ...data: MapEvents[E]) {
|
||||
const listeners = this._listeners[eventName] as Array<EventHandler<MapEvents, E>> | undefined;
|
||||
_simulateEvent<E extends EventName<SocketEvents>>(eventName: E, ...data: SocketEvents[E]) {
|
||||
const listeners = this._listeners[eventName] as Array<EventHandler<SocketEvents, E>> | undefined;
|
||||
if(listeners) {
|
||||
listeners.forEach(function(listener: EventHandler<MapEvents, E>) {
|
||||
listeners.forEach(function(listener: EventHandler<SocketEvents, E>) {
|
||||
listener(...data);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -5,96 +5,7 @@ import ng from 'angular';
|
|||
|
||||
fm.app.factory("fmHighlightableLayers", function(fmUtils) {
|
||||
|
||||
class GeoJSON extends L.GeoJSON {
|
||||
|
||||
addData(geojson) {
|
||||
var features = Array.isArray(geojson) ? geojson : geojson.features,
|
||||
i, len, feature;
|
||||
|
||||
if (features) {
|
||||
for (i = 0, len = features.length; i < len; i++) {
|
||||
// only add this if geometry or geometries are set and not null
|
||||
feature = features[i];
|
||||
if (feature.geometries || feature.geometry || feature.features || feature.coordinates) {
|
||||
this.addData(feature);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
var options = this.options;
|
||||
|
||||
if (options.filter && !options.filter(geojson)) { return this; }
|
||||
|
||||
var layer = this.geometryToLayer(geojson, options);
|
||||
if (!layer) {
|
||||
return this;
|
||||
}
|
||||
layer.feature = L.GeoJSON.asFeature(geojson);
|
||||
|
||||
layer.defaultOptions = layer.options;
|
||||
this.resetStyle(layer);
|
||||
|
||||
if (options.onEachFeature) {
|
||||
options.onEachFeature(geojson, layer);
|
||||
}
|
||||
|
||||
return this.addLayer(layer);
|
||||
}
|
||||
|
||||
geometryToLayer(geojson, options) {
|
||||
var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson,
|
||||
coords = geometry ? geometry.coordinates : null,
|
||||
layers = [],
|
||||
_coordsToLatLng = options && options.coordsToLatLng || L.GeoJSON.coordsToLatLng,
|
||||
latlng, latlngs, i, len;
|
||||
|
||||
if (!coords && !geometry) {
|
||||
return null;
|
||||
}
|
||||
|
||||
switch (geometry.type) {
|
||||
case 'Point':
|
||||
latlng = _coordsToLatLng(coords);
|
||||
return new fmHighlightableLayers.Marker(latlng, this.options.markerOptions);
|
||||
|
||||
case 'MultiPoint':
|
||||
for (i = 0, len = coords.length; i < len; i++) {
|
||||
latlng = _coordsToLatLng(coords[i]);
|
||||
layers.push(new fmHighlightableLayers.Marker(latlng, this.options.markerOptions));
|
||||
}
|
||||
return new FeatureGroup(layers);
|
||||
|
||||
case 'LineString':
|
||||
case 'MultiLineString':
|
||||
latlngs = L.GeoJSON.coordsToLatLngs(coords, geometry.type === 'LineString' ? 0 : 1, _coordsToLatLng);
|
||||
return new fmHighlightableLayers.Polyline(latlngs, this.options);
|
||||
|
||||
case 'Polygon':
|
||||
case 'MultiPolygon':
|
||||
latlngs = L.GeoJSON.coordsToLatLngs(coords, geometry.type === 'Polygon' ? 1 : 2, _coordsToLatLng);
|
||||
return new fmHighlightableLayers.Polygon(latlngs, this.options);
|
||||
|
||||
case 'GeometryCollection':
|
||||
for (i = 0, len = geometry.geometries.length; i < len; i++) {
|
||||
var layer = this.geometryToLayer({
|
||||
geometry: geometry.geometries[i],
|
||||
type: 'Feature',
|
||||
properties: geojson.properties
|
||||
}, options);
|
||||
|
||||
if (layer) {
|
||||
layers.push(layer);
|
||||
}
|
||||
}
|
||||
return new L.FeatureGroup(layers);
|
||||
|
||||
default:
|
||||
throw new Error('Invalid GeoJSON object.');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
let fmHighlightableLayers = {
|
||||
|
|
|
@ -18,34 +18,6 @@ fm.app.factory("fmMapLines", function(fmUtils, $uibModal, $compile, $timeout, $r
|
|||
linePopupBaseScope.client = map.client;
|
||||
linePopupBaseScope.className = css.className;
|
||||
|
||||
map.client.on("line", function(data) {
|
||||
setTimeout(function() { // trackPoints needs to be copied over
|
||||
if((!map.client._editingLineId || data.id != map.client._editingLineId) && map.client.filterFunc(map.client.lines[data.id]))
|
||||
linesUi._addLine(map.client.lines[data.id]);
|
||||
}, 0);
|
||||
});
|
||||
|
||||
map.client.on("deleteLine", function(data) {
|
||||
linesUi._deleteLine(data);
|
||||
});
|
||||
|
||||
map.client.on("linePoints", function(data) {
|
||||
setTimeout(function() {
|
||||
if((!map.client._editingLineId || data.id != map.client._editingLineId) && map.client.filterFunc(map.client.lines[data.id]))
|
||||
linesUi._addLine(map.client.lines[data.id]);
|
||||
}, 0);
|
||||
});
|
||||
|
||||
map.client.on("filter", function() {
|
||||
for(var i in map.client.lines) {
|
||||
var show = (!map.client._editingLineId || i != map.client._editingLineId) && map.client.filterFunc(map.client.lines[i]);
|
||||
if(linesById[i] && !show)
|
||||
linesUi._deleteLine(map.client.lines[i]);
|
||||
else if(!linesById[i] && show)
|
||||
linesUi._addLine(map.client.lines[i]);
|
||||
}
|
||||
});
|
||||
|
||||
map.mapEvents.$on("showObject", async (event, id, zoom) => {
|
||||
let m = id.match(/^l(\d+)$/);
|
||||
if(m) {
|
||||
|
@ -74,60 +46,8 @@ fm.app.factory("fmMapLines", function(fmUtils, $uibModal, $compile, $timeout, $r
|
|||
|
||||
var linesUi = {
|
||||
_addLine: function(line, _doNotRerenderPopup) {
|
||||
var trackPoints = [ ];
|
||||
var p = line.trackPoints || [ ];
|
||||
for(var i=0; i<p.length; i++) {
|
||||
if(p[i] != null)
|
||||
trackPoints.push(L.latLng(p[i].lat, p[i].lon));
|
||||
}
|
||||
|
||||
if(trackPoints.length < 2)
|
||||
return linesUi._deleteLine(line);
|
||||
|
||||
if(!linesById[line.id]) {
|
||||
linesById[line.id] = (new fmHighlightableLayers.Polyline([ ])).addTo(map.map);
|
||||
|
||||
if(line.id != null) { // We don't want a popup for lines that we are drawing right now
|
||||
linesById[line.id]
|
||||
.on("click", function(e) {
|
||||
linesUi.showLineInfoBox(map.client.lines[line.id]);
|
||||
}.fmWrapApply($rootScope))
|
||||
.bindTooltip("", $.extend({}, map.tooltipOptions, { sticky: true, offset: [ 20, 0 ] }))
|
||||
.on("tooltipopen", function(e) {
|
||||
linesById[line.id].setTooltipContent(fmUtils.quoteHtml(map.client.lines[line.id].name)).openTooltip(e.latlng);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var style = {
|
||||
color : '#'+line.colour,
|
||||
width : line.width
|
||||
};
|
||||
|
||||
if(line.id == null) // We are drawing a line
|
||||
style.highlight = true;
|
||||
|
||||
// Two points that are both outside of the viewport should not be connected, as the piece in between
|
||||
// has not been received.
|
||||
let splitLatLngs = fmUtils.disconnectSegmentsOutsideViewport(trackPoints, map.map.getBounds());
|
||||
|
||||
linesById[line.id].setLatLngs(splitLatLngs).setStyle(style);
|
||||
|
||||
if(line.id != null && openLine && line.id == openLine.id && !_doNotRerenderPopup)
|
||||
linesUi.showLineInfoBox(line);
|
||||
},
|
||||
_deleteLine: function(line) {
|
||||
if(line.id != null && openLine && line.id == openLine.id) {
|
||||
openLine.hide();
|
||||
openLine = null;
|
||||
}
|
||||
|
||||
var lineObj = linesById[line.id];
|
||||
if(!lineObj)
|
||||
return;
|
||||
|
||||
lineObj.removeFrom(map.map);
|
||||
delete linesById[line.id];
|
||||
},
|
||||
getZoomDestination(line) {
|
||||
let bounds = fmUtils.fmToLeafletBbox(line);
|
||||
|
|
|
@ -19,74 +19,6 @@ fmUtils.copyToClipboard = function(text) {
|
|||
c.destroy();
|
||||
};
|
||||
|
||||
fmUtils.temporaryDragMarker = function(map, line, colour, callback, additionalOptions) {
|
||||
// This marker is shown when we hover the line. It enables us to create new markers.
|
||||
// It is a huge one (a normal marker with 5000 px or so transparency around it, so that we can be
|
||||
// sure that the mouse is over it and dragging it will work smoothly.
|
||||
|
||||
let temporaryHoverMarker;
|
||||
let lastPos = null;
|
||||
|
||||
function update() {
|
||||
if(lastPos) {
|
||||
const pointOnLine = fmUtils.getClosestPointOnLine(map, line._latlngs[0], lastPos);
|
||||
const distance = map.latLngToContainerPoint(pointOnLine).distanceTo(map.latLngToContainerPoint(lastPos));
|
||||
if(distance > line.options.weight / 2)
|
||||
lastPos = null;
|
||||
else {
|
||||
temporaryHoverMarker.setLatLng(pointOnLine);
|
||||
if(!temporaryHoverMarker._map)
|
||||
temporaryHoverMarker.addTo(map);
|
||||
}
|
||||
}
|
||||
|
||||
if(!lastPos && temporaryHoverMarker._map)
|
||||
temporaryHoverMarker.remove();
|
||||
}
|
||||
|
||||
function _move(e) {
|
||||
lastPos = map.mouseEventToLatLng(e.originalEvent);
|
||||
update();
|
||||
}
|
||||
|
||||
function _out(e) {
|
||||
lastPos = null;
|
||||
setTimeout(update, 0); // Delay in case there is a mouseover event over the marker following
|
||||
}
|
||||
|
||||
line.on("mouseover", _move).on("mousemove", _move).on("mouseout", _out);
|
||||
|
||||
function makeTemporaryHoverMarker() {
|
||||
temporaryHoverMarker = L.marker([0,0], Object.assign({
|
||||
icon: fmUtils.createMarkerIcon(colour, 35, null, null, 1000),
|
||||
draggable: true,
|
||||
rise: true
|
||||
}, additionalOptions)).once("dragstart", function() {
|
||||
temporaryHoverMarker.once("dragend", function() {
|
||||
// We have to replace the huge icon with the regular one at the end of the dragging, otherwise
|
||||
// the dragging gets interrupted
|
||||
this.setIcon(fmUtils.createMarkerIcon(colour));
|
||||
}, temporaryHoverMarker);
|
||||
|
||||
callback(temporaryHoverMarker);
|
||||
|
||||
makeTemporaryHoverMarker();
|
||||
})
|
||||
.on("mouseover", _move).on("mousemove", _move).on("mouseout", _out)
|
||||
.on("click", (e) => {
|
||||
// Forward to the line to make it possible to click it again
|
||||
line.fire("click", e);
|
||||
});
|
||||
}
|
||||
|
||||
makeTemporaryHoverMarker();
|
||||
|
||||
return function() {
|
||||
line.off("mouseover", _move).off("mousemove", _move).off("mouseout", _out);
|
||||
temporaryHoverMarker.remove();
|
||||
};
|
||||
};
|
||||
|
||||
fmUtils.onLongMouseDown = function(map, callback) {
|
||||
var mouseDownTimeout, pos;
|
||||
|
||||
|
|
|
@ -41,12 +41,35 @@
|
|||
value="Open map wqxygV4R506PlBlZ"
|
||||
onclick="client.setPadId('wqxygV4R506PlBlZ').catch(log('setPadId error'))"
|
||||
/>
|
||||
<a href="http://localhost:40829/wqxygV4R506PlBlZ" target="_blank"><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAQElEQVR42qXKwQkAIAxDUUdxtO6/RBQkQZvSi8I/pL4BoGw/XPkh4XigPmsUgh0626AjRsgxHTkUThsG2T/sIlzdTsp52kSS1wAAAABJRU5ErkJggg=="></a>
|
||||
|
||||
<input
|
||||
type="button"
|
||||
value="Toggle filter name ~= "test""
|
||||
onclick="map.setFmFilter(map.fmFilter ? undefined : 'name ~= "test"')"
|
||||
/>
|
||||
|
||||
<input
|
||||
type="button"
|
||||
value="Berlin to Hamburg"
|
||||
onclick="client.setRoute({ routePoints: [{ lat: 52.51704, lon: 13.38886 }, { lat: 53.55034, lon: 10.00065 }], mode: 'car' })"
|
||||
/>
|
||||
<input
|
||||
type="button"
|
||||
value="Clear route"
|
||||
onclick="client.clearRoute()"
|
||||
/>
|
||||
|
||||
<input
|
||||
type="button"
|
||||
value="Search for "Berlin""
|
||||
onclick="client.find({ query: 'Berlin' }).then((res) => { searchResultsLayer.setResults(res) }).catch((err) => { console.error(err); })"
|
||||
/>
|
||||
<input
|
||||
type="button"
|
||||
value="Clear search"
|
||||
onclick="searchResultsLayer.setResults([]);"
|
||||
/>
|
||||
</div>
|
||||
<div id="map"></div>
|
||||
<script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"></script>
|
||||
|
@ -68,13 +91,35 @@
|
|||
|
||||
const markersLayer = new L.FacilMap.MarkersLayer(client).addTo(map)
|
||||
.on("click", (e) => {
|
||||
markersLayer.setHighlightedMarkers(new Set([e.layer.options.marker.id]));
|
||||
markersLayer.setHighlightedMarkers(new Set([e.layer.marker.id]));
|
||||
linesLayer.setHighlightedLines(new Set());
|
||||
searchResultsLayer.setHighlightedResults(new Set());
|
||||
});
|
||||
|
||||
const linesLayer = new L.FacilMap.LinesLayer(client).addTo(map)
|
||||
.on("click", (e) => {
|
||||
L.DomEvent.stopPropagation(e);
|
||||
markersLayer.setHighlightedMarkers(new Set());
|
||||
linesLayer.setHighlightedLines(new Set([e.layer.line.id]));
|
||||
searchResultsLayer.setHighlightedResults(new Set());
|
||||
});
|
||||
|
||||
map.on("click", () => {
|
||||
markersLayer.setHighlightedMarkers(new Set());
|
||||
linesLayer.setHighlightedLines(new Set());
|
||||
searchResultsLayer.setHighlightedResults(new Set());
|
||||
});
|
||||
|
||||
const routeLayer = new L.FacilMap.RouteLayer(client, { raised: true }).addTo(map);
|
||||
|
||||
const searchResultsLayer = new L.FacilMap.SearchResultsLayer().addTo(map)
|
||||
.on("click", (e) => {
|
||||
L.DomEvent.stopPropagation(e);
|
||||
markersLayer.setHighlightedMarkers(new Set());
|
||||
linesLayer.setHighlightedLines(new Set());
|
||||
searchResultsLayer.setHighlightedResults(new Set([ e.layer._fmSearchResult ]));
|
||||
});
|
||||
|
||||
const hashHandler = new L.FacilMap.HashHandler(map, client).enable();
|
||||
</script>
|
||||
</body>
|
||||
|
|
|
@ -5971,14 +5971,22 @@
|
|||
"integrity": "sha512-/xwPEBidtg69Q3HlqPdU3DnrXQOvQU/CCHA1tcDQVzOwm91YMYaILjNp7L4Eaw5Z4sOYdbBz6koWyibppd8Zqw=="
|
||||
},
|
||||
"leaflet-auto-graticule": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/leaflet-auto-graticule/-/leaflet-auto-graticule-1.0.6.tgz",
|
||||
"integrity": "sha512-A4CL5MmI0B8aiWINULzUEWZadRoPvTzYwI5an/kHfXvNNaut2z5HN+RSPwvRx0Lp824epHTbETXGbxHikpBWUQ=="
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/leaflet-auto-graticule/-/leaflet-auto-graticule-1.0.7.tgz",
|
||||
"integrity": "sha512-GZYMh62MqZ1WZv5D4zx0xR/7LFpf1lzmSwc/gYrc/8CeQtks8kL9E95K4DhE7MC3LsNf9Oa22rj+MBexkR0TFw=="
|
||||
},
|
||||
"leaflet-draggable-lines": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/leaflet-draggable-lines/-/leaflet-draggable-lines-1.0.5.tgz",
|
||||
"integrity": "sha512-pJzrAG7HkVcsHl/NZF2VK7h0SviMMrMf20x7RkR/cimF2mv5zxGvfi3n0yoWsqUM4OhUGjqqez3v5f9JgZUnZA==",
|
||||
"requires": {
|
||||
"leaflet-geometryutil": "^0.9.3"
|
||||
}
|
||||
},
|
||||
"leaflet-freie-tonne": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/leaflet-freie-tonne/-/leaflet-freie-tonne-1.0.1.tgz",
|
||||
"integrity": "sha512-hRpIJWJbu6D2miAPBOQc1fZPrtlawXOameHxZt+uYEcq3JKKAeQ6WiG9GsQmsWkuxIT6PWlmrebi6+CgIkPc+A=="
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/leaflet-freie-tonne/-/leaflet-freie-tonne-1.0.3.tgz",
|
||||
"integrity": "sha512-SF/LMSW4zwOYib/9+X6mAwZBxNnffWO8fVvBkjraTlr7e41zkCsE+7W4UVvJu2GXsVqIzx9NgZBBi5dvCDOWug=="
|
||||
},
|
||||
"leaflet-geometryutil": {
|
||||
"version": "0.9.3",
|
||||
|
@ -5994,9 +6002,9 @@
|
|||
"integrity": "sha1-w2xxg0fFJDAztXy0uuomEZ2CxwE="
|
||||
},
|
||||
"leaflet-highlightable-layers": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/leaflet-highlightable-layers/-/leaflet-highlightable-layers-1.0.1.tgz",
|
||||
"integrity": "sha512-E/dtm5iscM5HQSNkyfy8Xh4AvFJNrCzBJoNqkc7x6MgI1pGdjBxn4RTCP2jLkg1Lt2WMF8HATc2Y1w4zSMw4sA=="
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/leaflet-highlightable-layers/-/leaflet-highlightable-layers-1.0.7.tgz",
|
||||
"integrity": "sha512-/TIuwKnCQudPUOpTz7QO/8qjsNkcWIvHgPvTa5xoGbex8EsAhyxVPU70qurfeq63+zqQQFzIYV6b1Vu165A6Lw=="
|
||||
},
|
||||
"leaflet.markercluster": {
|
||||
"version": "1.4.1",
|
||||
|
|
|
@ -33,11 +33,12 @@
|
|||
"facilmap-types": "2.7.0",
|
||||
"filtrex": "^2.1.0",
|
||||
"leaflet": "^1.7.1",
|
||||
"leaflet-auto-graticule": "^1.0.6",
|
||||
"leaflet-freie-tonne": "^1.0.1",
|
||||
"leaflet-auto-graticule": "^1.0.7",
|
||||
"leaflet-draggable-lines": "^1.0.5",
|
||||
"leaflet-freie-tonne": "^1.0.3",
|
||||
"leaflet-geometryutil": "^0.9.3",
|
||||
"leaflet-hash": "^0.2.1",
|
||||
"leaflet-highlightable-layers": "^1.0.1",
|
||||
"leaflet-highlightable-layers": "^1.0.7",
|
||||
"leaflet.markercluster": "^1.4.1",
|
||||
"lodash": "^4.17.20"
|
||||
},
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import Socket from "facilmap-client";
|
||||
import { EventHandler, MapEvents } from "facilmap-types";
|
||||
import Socket, { SocketEvents } from "facilmap-client";
|
||||
import { EventHandler } from "facilmap-types";
|
||||
import { Handler, Map } from "leaflet";
|
||||
import { leafletToFmBbox } from "./utils/leaflet";
|
||||
|
||||
|
@ -22,7 +22,7 @@ export default class BboxHandler extends Handler {
|
|||
}
|
||||
}
|
||||
|
||||
handleEmit: EventHandler<MapEvents, "emit"> = (name, data) => {
|
||||
handleEmit: EventHandler<SocketEvents, "emit"> = (name, data) => {
|
||||
if (["setPadId", "setRoute"].includes(name)) {
|
||||
this.updateBbox();
|
||||
}
|
||||
|
|
|
@ -13,15 +13,23 @@ import MarkerCluster from "./markers/marker-cluster";
|
|||
import MarkerLayer from "./markers/marker-layer";
|
||||
import MarkersLayer from "./markers/markers-layer";
|
||||
import HashHandler from "./views/hash";
|
||||
import LinesLayer from "./lines/lines-layer";
|
||||
import RouteLayer from "./lines/route-layer";
|
||||
import SearchResultGeoJSON from "./search/search-result-geojson";
|
||||
import SearchResultsLayer from "./search/search-results-layer";
|
||||
|
||||
const FacilMap = {
|
||||
BboxHandler,
|
||||
ClickListener,
|
||||
HashHandler,
|
||||
Layers,
|
||||
LinesLayer,
|
||||
MarkerCluster,
|
||||
MarkerLayer,
|
||||
MarkersLayer,
|
||||
RouteLayer,
|
||||
SearchResultGeoJSON,
|
||||
SearchResultsLayer,
|
||||
Socket,
|
||||
Utils: {
|
||||
leaflet: leafletUtils,
|
||||
|
|
|
@ -0,0 +1,146 @@
|
|||
import Socket, { TrackPoints } from "facilmap-client";
|
||||
import { ID, Line, LinePointsEvent, ObjectWithId } from "facilmap-types";
|
||||
import { FeatureGroup, LayerOptions, Map, PolylineOptions } from "leaflet";
|
||||
import { HighlightableLayerOptions, HighlightablePolyline } from "leaflet-highlightable-layers";
|
||||
import { disconnectSegmentsOutsideViewport, tooltipOptions, trackPointsToLatLngArray } from "../utils/leaflet";
|
||||
import { quoteHtml } from "../utils/utils";
|
||||
|
||||
interface LinesLayerOptions extends LayerOptions {
|
||||
}
|
||||
|
||||
export default class LinesLayer extends FeatureGroup {
|
||||
|
||||
options!: LayerOptions;
|
||||
client: Socket;
|
||||
linesById: Record<string, InstanceType<typeof HighlightablePolyline>> = {};
|
||||
highlightedLinesIds = new Set<ID>();
|
||||
|
||||
constructor(client: Socket, options?: LinesLayerOptions) {
|
||||
super([], options);
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
onAdd(map: Map) {
|
||||
super.onAdd(map);
|
||||
|
||||
this.client.on("line", this.handleLine);
|
||||
this.client.on("linePoints", this.handleLinePoints);
|
||||
this.client.on("deleteLine", this.handleDeleteLine);
|
||||
|
||||
map.on("fmFilter", this.handleFilter);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
onRemove(map: Map) {
|
||||
super.onRemove(map);
|
||||
|
||||
this.client.removeListener("line", this.handleLine);
|
||||
this.client.removeListener("linePoints", this.handleLinePoints);
|
||||
this.client.removeListener("deleteLine", this.handleDeleteLine);
|
||||
|
||||
map.off("fmFilter", this.handleFilter);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
handleLine = (line: Line) => {
|
||||
if(this._map.fmFilterFunc(line))
|
||||
this._addLine(line);
|
||||
};
|
||||
|
||||
handleLinePoints = (event: LinePointsEvent) => {
|
||||
const line = this.client.lines[event.id];
|
||||
if(line && this._map.fmFilterFunc(line))
|
||||
this._addLine(line);
|
||||
};
|
||||
|
||||
handleDeleteLine = (data: ObjectWithId) => {
|
||||
this._deleteLine(data);
|
||||
};
|
||||
|
||||
handleFilter = () => {
|
||||
for(const i of Object.keys(this.client.lines) as any as Array<keyof Socket['lines']>) {
|
||||
const show = this._map.fmFilterFunc(this.client.lines[i]);
|
||||
if(this.linesById[i] && !show)
|
||||
this._deleteLine(this.client.lines[i]);
|
||||
else if(!this.linesById[i] && show)
|
||||
this._addLine(this.client.lines[i]);
|
||||
}
|
||||
};
|
||||
|
||||
highlightLine(id: ID) {
|
||||
this.highlightedLinesIds.add(id);
|
||||
if (this.client.lines[id])
|
||||
this.handleLine(this.client.lines[id]);
|
||||
}
|
||||
|
||||
unhighlightLine(id: ID) {
|
||||
this.highlightedLinesIds.delete(id);
|
||||
if (this.client.lines[id])
|
||||
this.handleLine(this.client.lines[id]);
|
||||
}
|
||||
|
||||
setHighlightedLines(ids: Set<ID>) {
|
||||
for (const id of this.highlightedLinesIds) {
|
||||
if (!ids.has(id))
|
||||
this.unhighlightLine(id);
|
||||
}
|
||||
|
||||
for (const id of ids) {
|
||||
if (!this.highlightedLinesIds.has(id))
|
||||
this.highlightLine(id);
|
||||
}
|
||||
}
|
||||
|
||||
_addLine(line: Line & { trackPoints?: TrackPoints }) {
|
||||
const trackPoints = trackPointsToLatLngArray(line.trackPoints);
|
||||
|
||||
if(trackPoints.length < 2) {
|
||||
this._deleteLine(line);
|
||||
return;
|
||||
}
|
||||
|
||||
if(!this.linesById[line.id]) {
|
||||
this.linesById[line.id] = new HighlightablePolyline([ ]);
|
||||
this.addLayer(this.linesById[line.id]);
|
||||
|
||||
if(line.id != null) { // We don't want a popup for lines that we are drawing right now
|
||||
this.linesById[line.id]
|
||||
.bindTooltip("", { ...tooltipOptions, sticky: true, offset: [ 20, 0 ] })
|
||||
.on("tooltipopen", () => {
|
||||
this.linesById[line.id].setTooltipContent(quoteHtml(this.client.lines[line.id].name));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const style: HighlightableLayerOptions<PolylineOptions> = {
|
||||
color: '#'+line.colour,
|
||||
weight: line.width,
|
||||
opacity: 0.35
|
||||
} as any;
|
||||
|
||||
if(line.id == null || this.highlightedLinesIds.has(line.id)) {
|
||||
Object.assign(style, {
|
||||
raised: true,
|
||||
opacity: 1
|
||||
});
|
||||
}
|
||||
|
||||
// Two points that are both outside of the viewport should not be connected, as the piece in between
|
||||
// has not been received.
|
||||
let splitLatLngs = disconnectSegmentsOutsideViewport(trackPoints, this._map.getBounds());
|
||||
|
||||
(this.linesById[line.id] as any).line = line;
|
||||
this.linesById[line.id].setLatLngs(splitLatLngs).setStyle(style);
|
||||
}
|
||||
|
||||
_deleteLine(line: ObjectWithId) {
|
||||
if(!this.linesById[line.id])
|
||||
return;
|
||||
|
||||
this.removeLayer(this.linesById[line.id]);
|
||||
delete this.linesById[line.id];
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
import Socket from "facilmap-client";
|
||||
import { Map, PathOptions, PolylineOptions } from "leaflet";
|
||||
import { HighlightableLayerOptions, HighlightablePolyline } from "leaflet-highlightable-layers";
|
||||
import { trackPointsToLatLngArray } from "../utils/leaflet";
|
||||
import DraggableLines from "leaflet-draggable-lines";
|
||||
|
||||
interface RouteLayerOptions extends PolylineOptions {
|
||||
}
|
||||
|
||||
export default class RouteLayer extends HighlightablePolyline {
|
||||
|
||||
realOptions!: RouteLayerOptions;
|
||||
client: Socket;
|
||||
draggable?: DraggableLines;
|
||||
|
||||
constructor(client: Socket, options?: RouteLayerOptions) {
|
||||
super([], options);
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
onAdd(map: Map) {
|
||||
super.onAdd(map);
|
||||
|
||||
this.draggable = new DraggableLines(map, { enableForLayer: false });
|
||||
this.draggable.enable();
|
||||
this.draggable.on("dragend remove insert", this.handleDrag);
|
||||
this.updateDraggableStyle();
|
||||
|
||||
this.client.on("route", this.handleRoute);
|
||||
this.client.on("routePoints", this.handleRoutePoints);
|
||||
this.updateLine(true);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
onRemove(map: Map) {
|
||||
super.onRemove(map);
|
||||
|
||||
this.client.removeListener("route", this.handleRoute);
|
||||
this.client.removeListener("routePoints", this.handleRoutePoints);
|
||||
|
||||
this.draggable!.off("dragend remove insert", this.handleDrag);
|
||||
this.draggable!.disableForLayer(this);
|
||||
this.draggable!.disable();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
handleDrag = () => {
|
||||
this.updateRoute();
|
||||
};
|
||||
|
||||
handleRoute = () => {
|
||||
this.updateLine(true);
|
||||
};
|
||||
|
||||
handleRoutePoints = () => {
|
||||
this.updateLine(false);
|
||||
};
|
||||
|
||||
updateRoute() {
|
||||
if (this.client.route) {
|
||||
this.client.setRoute({
|
||||
...this.client.route,
|
||||
routePoints: this.getDraggableLinesRoutePoints()!.map((p) => ({ lat: p.lat, lon: p.lng }))
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
updateLine(updateRoutePoints: boolean) {
|
||||
if (this.client.route) {
|
||||
if (updateRoutePoints)
|
||||
this.setDraggableLinesRoutePoints(this.client.route.routePoints.map((p) => [p.lat, p.lon]));
|
||||
|
||||
const trackPoints = trackPointsToLatLngArray(this.client.route.trackPoints);
|
||||
this.setLatLngs(trackPoints);
|
||||
|
||||
this.draggable!.enableForLayer(this);
|
||||
} else {
|
||||
this.setLatLngs([]);
|
||||
this.draggable!.disableForLayer(this);
|
||||
}
|
||||
}
|
||||
|
||||
updateDraggableStyle() {
|
||||
if (this.draggable) {
|
||||
Object.assign(this.draggable.options, {
|
||||
dragMarkerOptions: () => ({ pane: "fm-raised-marker" }),
|
||||
tempMarkerOptions: () => ({ pane: "fm-raised-marker" }),
|
||||
plusTempMarkerOptions: () => ({ pane: "fm-raised-marker" })
|
||||
});
|
||||
this.draggable.redraw();
|
||||
}
|
||||
}
|
||||
|
||||
setStyle(style: HighlightableLayerOptions<PathOptions>) {
|
||||
super.setStyle(style);
|
||||
|
||||
this.updateDraggableStyle();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import { Colour, Marker, Shape, Symbol } from "facilmap-types";
|
||||
import { Marker } from "facilmap-types";
|
||||
import L, { LatLngExpression, LeafletMouseEvent, Map, Marker as LeafletMarker, MarkerOptions } from "leaflet";
|
||||
import { createMarkerIcon } from "../utils/icons";
|
||||
import { setLayerPane } from "leaflet-highlightable-layers";
|
||||
|
@ -9,7 +9,7 @@ Map.addInitHook(function (this: Map) {
|
|||
});
|
||||
|
||||
export interface MarkerLayerOptions extends MarkerOptions {
|
||||
marker?: Marker;
|
||||
marker?: Partial<Marker> & Pick<Marker, 'colour' | 'size' | 'symbol' | 'shape'>;
|
||||
padding?: number;
|
||||
highlight?: boolean;
|
||||
raised?: boolean;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import Socket from 'facilmap-client';
|
||||
import { ID, Marker, ObjectWithId } from 'facilmap-types';
|
||||
import { FeatureGroup, LayerOptions, Map } from 'leaflet';
|
||||
import { Map } from 'leaflet';
|
||||
import { tooltipOptions } from '../utils/leaflet';
|
||||
import { quoteHtml } from '../utils/utils';
|
||||
import MarkerCluster, { MarkerClusterOptions } from './marker-cluster';
|
||||
|
@ -107,11 +107,16 @@ export default class MarkersLayer extends MarkerCluster {
|
|||
});
|
||||
}
|
||||
|
||||
(this.markersById[marker.id] as any).marker = marker;
|
||||
|
||||
const highlight = this.highlightedMarkerIds.has(marker.id);
|
||||
|
||||
this.markersById[marker.id]
|
||||
.setLatLng([ marker.lat, marker.lon ])
|
||||
.setStyle({
|
||||
marker,
|
||||
highlight: this.highlightedMarkerIds.has(marker.id)
|
||||
highlight,
|
||||
raised: highlight
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
import { GeoJSON, Geometry, Feature } from "geojson";
|
||||
import { FeatureGroup, GeoJSON as GeoJSONLayer, GeoJSONOptions, Layer } from "leaflet";
|
||||
import { HighlightablePolygon, HighlightablePolyline } from "leaflet-highlightable-layers";
|
||||
import MarkerLayer, { MarkerLayerOptions } from "../markers/marker-layer";
|
||||
|
||||
interface SearchResultGeoJSONOptions extends GeoJSONOptions {
|
||||
marker?: MarkerLayerOptions['marker'];
|
||||
highlight?: boolean;
|
||||
raised?: boolean;
|
||||
}
|
||||
|
||||
export default class SearchResultGeoJSON extends GeoJSONLayer {
|
||||
|
||||
options!: SearchResultGeoJSONOptions;
|
||||
|
||||
constructor(geojson: GeoJSON, options?: SearchResultGeoJSONOptions) {
|
||||
super(geojson, options);
|
||||
}
|
||||
|
||||
addData(geojson: GeoJSON) {
|
||||
// GeoJSON.addData() does not support specifying a custom geometryToLayer function. Thus we are replicating its functionality here.
|
||||
|
||||
if (Array.isArray(geojson) || 'features' in geojson) {
|
||||
for (const feature of Array.isArray(geojson) ? geojson : geojson.features) {
|
||||
if (feature.geometries || feature.geometry || feature.features || feature.coordinates) {
|
||||
this.addData(feature);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
if (this.options.filter && !this.options.filter(geojson as any))
|
||||
return this;
|
||||
|
||||
const layer = this.geometryToLayer(geojson);
|
||||
if (!layer)
|
||||
return this;
|
||||
|
||||
(layer as any).feature = GeoJSONLayer.asFeature(geojson);
|
||||
|
||||
(layer as any).defaultOptions = layer.options;
|
||||
this.resetStyle(layer);
|
||||
|
||||
if (this.options.onEachFeature)
|
||||
this.options.onEachFeature(geojson as any, layer);
|
||||
|
||||
return this.addLayer(layer);
|
||||
}
|
||||
|
||||
geometryToLayer(geojson: Geometry | Feature): Layer | undefined {
|
||||
const geometry = geojson.type === 'Feature' ? geojson.geometry : geojson;
|
||||
const _coordsToLatLng = this.options.coordsToLatLng || GeoJSONLayer.coordsToLatLng;
|
||||
|
||||
if (!geometry)
|
||||
return;
|
||||
|
||||
switch (geometry.type) {
|
||||
case 'Point':
|
||||
return new MarkerLayer(_coordsToLatLng(geometry.coordinates as any), { marker: this.options.marker, raised: this.options.raised, highlight: this.options.highlight });
|
||||
|
||||
case 'MultiPoint':
|
||||
return new FeatureGroup(geometry.coordinates.map((coords) => (
|
||||
new MarkerLayer(_coordsToLatLng(coords as any), { marker: this.options.marker, raised: this.options.raised, highlight: this.options.highlight })
|
||||
)));
|
||||
|
||||
case 'LineString':
|
||||
case 'MultiLineString':
|
||||
return new HighlightablePolyline(GeoJSONLayer.coordsToLatLngs(geometry.coordinates, geometry.type === 'LineString' ? 0 : 1, _coordsToLatLng), { raised: this.options.raised, opacity: this.options.highlight ? 1 : 0.35 });
|
||||
|
||||
case 'Polygon':
|
||||
case 'MultiPolygon':
|
||||
return new HighlightablePolygon(GeoJSONLayer.coordsToLatLngs(geometry.coordinates, geometry.type === 'Polygon' ? 1 : 2, _coordsToLatLng), { raised: this.options.raised, opacity: this.options.highlight ? 1 : 0.35 });
|
||||
|
||||
case 'GeometryCollection':
|
||||
return new FeatureGroup(geometry.geometries.map((g) => (
|
||||
this.geometryToLayer({
|
||||
geometry: g,
|
||||
type: 'Feature',
|
||||
properties: (geojson as any).properties
|
||||
})
|
||||
)).filter((l) => l) as Layer[]);
|
||||
|
||||
default:
|
||||
throw new Error('Invalid GeoJSON object.');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,114 @@
|
|||
import { SearchResult } from "facilmap-types";
|
||||
import { FeatureGroup, Layer, LayerOptions } from "leaflet";
|
||||
import MarkerLayer from "../markers/marker-layer";
|
||||
import { tooltipOptions } from "../utils/leaflet";
|
||||
import SearchResultGeoJSON from "./search-result-geojson";
|
||||
|
||||
declare module "leaflet" {
|
||||
interface Layer {
|
||||
_fmSearchResult?: SearchResult;
|
||||
}
|
||||
}
|
||||
|
||||
const searchMarkerColour = "000000";
|
||||
const searchMarkerSize = 35;
|
||||
|
||||
interface SearchResultsLayerOptions extends LayerOptions {
|
||||
}
|
||||
|
||||
export default class SearchResultsLayer extends FeatureGroup {
|
||||
|
||||
options!: SearchResultsLayerOptions;
|
||||
highlightedResults = new Set<SearchResult>();
|
||||
|
||||
constructor(results?: SearchResult[], options?: SearchResultsLayerOptions) {
|
||||
super([], options);
|
||||
|
||||
if (results)
|
||||
this.setResults(results);
|
||||
}
|
||||
|
||||
highlightResult(result: SearchResult) {
|
||||
this.highlightedResults.add(result);
|
||||
this.redrawResult(result);
|
||||
}
|
||||
|
||||
unhighlightResult(result: SearchResult) {
|
||||
this.highlightedResults.delete(result);
|
||||
this.redrawResult(result);
|
||||
}
|
||||
|
||||
setHighlightedResults(results: Set<SearchResult>) {
|
||||
for (const result of this.highlightedResults) {
|
||||
if (!results.has(result))
|
||||
this.unhighlightResult(result);
|
||||
}
|
||||
|
||||
for (const result of results) {
|
||||
if (!this.highlightedResults.has(result))
|
||||
this.highlightResult(result);
|
||||
}
|
||||
}
|
||||
|
||||
redrawResult(result: SearchResult) {
|
||||
for (const layer of this.getLayers().filter((layer) => layer._fmSearchResult === result)) {
|
||||
this.removeLayer(layer);
|
||||
}
|
||||
|
||||
for (const layer of this.resultToLayers(result)) {
|
||||
this.addLayer(layer);
|
||||
}
|
||||
}
|
||||
|
||||
resultToLayers(result: SearchResult) {
|
||||
const layers: Layer[] = [];
|
||||
|
||||
const highlight = this.highlightedResults.has(result);
|
||||
|
||||
if(!result.lat || !result.lon || (result.geojson && result.geojson.type != "Point")) { // If the geojson is just a point, we already render our own marker
|
||||
const layer = new SearchResultGeoJSON(result.geojson!, {
|
||||
raised: highlight,
|
||||
highlight,
|
||||
marker: {
|
||||
colour: searchMarkerColour,
|
||||
size: searchMarkerSize,
|
||||
symbol: result.icon || '',
|
||||
shape: ''
|
||||
}
|
||||
}).bindTooltip(result.display_name, { ...tooltipOptions, sticky: true, offset: [ 20, 0 ] })
|
||||
layer._fmSearchResult = result;
|
||||
layer.eachLayer((l) => {
|
||||
l._fmSearchResult = result;
|
||||
});
|
||||
layers.push(layer);
|
||||
}
|
||||
|
||||
if(result.lat != null && result.lon != null) {
|
||||
const marker = new MarkerLayer([ result.lat, result.lon ], {
|
||||
raised: highlight,
|
||||
highlight,
|
||||
marker: {
|
||||
colour: searchMarkerColour,
|
||||
size: searchMarkerSize,
|
||||
symbol: result.icon || '',
|
||||
shape: ''
|
||||
}
|
||||
}).bindTooltip(result.display_name, { ...tooltipOptions, offset: [ 20, 0 ] })
|
||||
marker._fmSearchResult = result;
|
||||
layers.push(marker);
|
||||
}
|
||||
|
||||
return layers;
|
||||
}
|
||||
|
||||
setResults(results: SearchResult[]) {
|
||||
this.clearLayers();
|
||||
|
||||
for (const result of results) {
|
||||
for (const layer of this.resultToLayers(result)) {
|
||||
this.addLayer(layer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
import { GridLayerOptions, Layer, Map } from "leaflet";
|
||||
import geojson from "geojson";
|
||||
|
||||
declare module "leaflet" {
|
||||
interface LayerOptions {
|
||||
|
@ -28,5 +29,11 @@ declare module "leaflet" {
|
|||
options: MarkerClusterGroupOptions;
|
||||
}
|
||||
|
||||
interface GeoJSON<P = any> {
|
||||
// Cannot override this properly
|
||||
//constructor(geojson?: Array<geojson.Feature> | geojson.GeoJSON, options?: GeoJSONOptions<P>);
|
||||
//addData(geojson: Array<geojson.Feature> | geojson.GeoJSON): this;
|
||||
}
|
||||
|
||||
export const Hash: any;
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
import { TrackPoints } from 'facilmap-client';
|
||||
import { Bbox, BboxWithZoom } from 'facilmap-types';
|
||||
import L, { LatLng, LatLngBounds, Map, TooltipOptions } from 'leaflet';
|
||||
import 'leaflet-geometryutil';
|
||||
|
@ -26,56 +27,6 @@ export function fmToLeafletBbox(bbox: Bbox): LatLngBounds {
|
|||
return L.latLngBounds(L.latLng(bbox.bottom, bbox.left), L.latLng(bbox.top, bbox.right));
|
||||
}
|
||||
|
||||
export function getClosestPointOnLine(map: Map, trackPoints: LatLng[], point: LatLng): LatLng {
|
||||
const index = getClosestIndexOnLine(map, trackPoints, point);
|
||||
const before = trackPoints[Math.floor(index)];
|
||||
const after = trackPoints[Math.ceil(index)];
|
||||
const percentage = index - Math.floor(index);
|
||||
return L.latLng(before.lat + percentage * (after.lat - before.lat), before.lng + percentage * (after.lng - before.lng));
|
||||
}
|
||||
|
||||
export function getClosestIndexOnLine(map: Map, trackPoints: LatLng[], point: LatLng, startI?: number): number {
|
||||
let dist = Infinity;
|
||||
let idx = null;
|
||||
|
||||
for(let i=(startI || 0); i<trackPoints.length-1; i++) {
|
||||
const thisDist = L.GeometryUtil.distanceSegment(map, point, trackPoints[i], trackPoints[i+1]);
|
||||
if(thisDist < dist) {
|
||||
dist = thisDist;
|
||||
idx = i;
|
||||
}
|
||||
}
|
||||
|
||||
if(idx == null)
|
||||
return trackPoints.length;
|
||||
|
||||
const closestPointOnSegment = L.GeometryUtil.closestOnSegment(map, point, trackPoints[idx], trackPoints[idx+1]);
|
||||
idx += L.GeometryUtil.distance(map, closestPointOnSegment, trackPoints[idx]) / L.GeometryUtil.distance(map, trackPoints[idx], trackPoints[idx+1]);
|
||||
|
||||
return idx;
|
||||
}
|
||||
|
||||
export function getIndexOnLine(map: Map, trackPoints: LatLng[], routePoints: LatLng[], point: LatLng): number {
|
||||
if(routePoints.length == 0)
|
||||
return 0;
|
||||
|
||||
const idxs: number[] = [ ];
|
||||
for(let i=0; i<routePoints.length; i++) {
|
||||
idxs.push(getClosestIndexOnLine(map, trackPoints, routePoints[i], Math.floor(idxs[i-1])));
|
||||
}
|
||||
|
||||
const pointIdx = getClosestIndexOnLine(map, trackPoints, point);
|
||||
|
||||
if(pointIdx == 0)
|
||||
return 0;
|
||||
|
||||
for(let i=0; i<idxs.length; i++) {
|
||||
if(idxs[i] > pointIdx)
|
||||
return i;
|
||||
}
|
||||
return idxs.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes an array of track points and splits it up where two points in a row are outside of the given bbox.
|
||||
* @param trackPoints {Array<L.LatLng>}
|
||||
|
@ -122,3 +73,15 @@ export function pointsEqual(latLng1: LatLng, latLng2: LatLng, map: Map, zoom?: n
|
|||
|
||||
return map.project(latLng1, zoom).distanceTo(map.project(latLng2, zoom)) < 1;
|
||||
}
|
||||
|
||||
export function trackPointsToLatLngArray(trackPoints: TrackPoints | undefined): LatLng[] {
|
||||
const result: LatLng[] = [];
|
||||
if (trackPoints) {
|
||||
for (let i = 0; i < trackPoints.length; i++) {
|
||||
if (trackPoints[i]) {
|
||||
result.push(new LatLng(trackPoints[i]!.lat, trackPoints[i]!.lon, trackPoints[i]!.ele));
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
|
@ -1,3 +1,5 @@
|
|||
import { TrackPoints } from "facilmap-client";
|
||||
|
||||
const LETTERS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||
const LENGTH = 12;
|
||||
|
||||
|
|
|
@ -27,7 +27,6 @@ export interface MapEvents {
|
|||
type: [Type];
|
||||
deleteType: [ObjectWithId];
|
||||
history: [HistoryEntry];
|
||||
emit: { [eventName in RequestName]: [eventName, RequestData<eventName>] }[RequestName];
|
||||
}
|
||||
|
||||
export type EventName<Events extends Record<keyof Events, any[]>> = keyof Events & string;
|
||||
|
|
Ładowanie…
Reference in New Issue