class Point implements L.LatLngLiteral { lat: number = 0.00; lng: number = 0.00; } enum OverlayType { POINT = 0, CIRCLE = 1, POLYGON = 2, POLYLINE = 3, } interface OverlayMap { [listName: string]: OverlayType; } interface Overlay { name: string; desc: string; points: Point[]; options: any; } class OverlayData implements Overlay { name: string; desc: string; points: Point[]; options: any; type: OverlayType; constructor(type: OverlayType, name: string, desc: string, points: Point[], options: any) { this.type = type; this.name = name; this.desc = desc; this.points = points; this.options = options; } } abstract class OverlayBase implements Overlay { name: string; desc: string; points: Point[]; options: any; protected self: any; constructor(name: string, desc: string, points: Point[], options: any) { this.name = name; this.desc = desc; this.points = points; this.options = options; } center(): Point { return this.points[0]; } static centerAsString(pt: Point): string { let eastWest = ""; let northSouth = ""; const lat = pt.lat; const long = pt.lng; if (lat > 0) { northSouth = "N"; } else if (lat < 0) { northSouth = "S"; } if (long > 0) { eastWest = "E"; } else if (long < 0) { eastWest = "W"; } return `${String(long).substring(0, 7)}°${eastWest}, ${String(lat).substring(0,7)}°${northSouth}`; } setPopupContent(content: string): void { this.self.bindPopup(content); } abstract add(map: L.Map): void; abstract remove(map: L.Map): void; abstract menuItem: Node | null; private static classSanitize(input: string): string { return input.replace(/\-/g, "_"); } private static overlayTypeMap: OverlayMap = { markers_list: OverlayType.POINT, circles_list: OverlayType.CIRCLE, polygons_list: OverlayType.POLYGON, } static listAdd(self: OverlayBase, listName: string) { const list = document.getElementById(listName); if (list) { const li = document.createElement("li"); const a = document.createElement("a"); if (li && a) { a.innerText = self.name; a.href = "#"; a.onclick = (e: any) => { MapHandler.editOverlay(self, OverlayBase.overlayTypeMap[OverlayBase.classSanitize(listName)]); }; li.appendChild(a); list.appendChild(li); self.menuItem = li; } } } static listRemove(self: OverlayBase, listName: string) { const list = document.getElementById(listName); if (list && self.menuItem) { list.removeChild(self.menuItem); } } } class Marker extends OverlayBase { menuItem: Node | null = null; constructor(name: string, desc: string, point: Point, options: any) { super(name, desc, [ point ], options); this.self = L.marker(point); this.self.bindPopup(`

${name}

${desc}

`); } add(map: L.Map) { this.self.addTo(map); OverlayBase.listAdd(this, "markers-list"); } remove(map: L.Map) { this.self.removeFrom(map); OverlayBase.listRemove(this, "markers-list"); } } class Circle extends OverlayBase { menuItem: Node | null = null; constructor(name: string, desc: string, point: Point, options: any) { super(name, desc, [ point ], options); this.self = L.circle(point, options); this.self.bindPopup(`

${name}

${desc}

`); } add(map: L.Map) { this.self.addTo(map); OverlayBase.listAdd(this, "circles-list"); } remove(map: L.Map) { this.self.removeFrom(map); OverlayBase.listRemove(this, "circles-list"); } } class Polygon extends OverlayBase { menuItem: Node | null = null; constructor(name: string, desc: string, points: Point[], options: any) { super(name, desc, points, options); if (options.closed) { this.self = L.polygon(points, options); } else { this.self = L.polyline(points, options); } this.self.bindPopup(`

${name}

${desc}

`); } center(): Point { return this.self.getCenter(); } add(map: L.Map) { this.self.addTo(map); OverlayBase.listAdd(this, "polygons-list"); } remove(map: L.Map) { this.self.removeFrom(map); OverlayBase.listRemove(this, "polygons-list"); } } class Polyline extends OverlayBase { menuItem: Node | null = null; constructor() { super("", "", [ ], {}); this.self = L.polyline([]); } insertPoint(pt: Point): void { this.self.addLatLng(pt); this.points.push(pt); } clearPoints(): void { this.points = []; this.self.setLatLngs([]); } numPoints(): number { return this.self.getLatLngs().length; } center(): Point { return this.self.getCenter(); } add(map: L.Map) { this.self.addTo(map); } remove(map: L.Map) { this.self.removeFrom(map); } } class OverlayState { markers: Marker[]; circles: Circle[]; paths: Polygon[]; polyline: Polyline; constructor() { this.markers = []; this.circles = []; this.paths = []; this.polyline = new Polyline(); } static load(): OverlayState { const store = localStorage.getItem("overlay_state"); return store ? OverlayState.import(store) : new OverlayState; } static import(overlayData: string): OverlayState { const model = JSON.parse(overlayData); return { markers: model.markers.map((m: OverlayData) => OverlayState.fromData(m)), circles: model.circles.map((c: OverlayData) => OverlayState.fromData(c)), paths: model.paths.map((p: OverlayData) => OverlayState.fromData(p)), polyline: new Polyline(), } as OverlayState } static exportSingle(overlay: OverlayBase): string { return JSON.stringify(OverlayState.toData(overlay), null, 2); } static export(overlayState: OverlayState): string { return JSON.stringify({ markers: overlayState.markers.map((m: OverlayBase) => OverlayState.toData(m)), circles: overlayState.circles.map((c: OverlayBase) => OverlayState.toData(c)), paths: overlayState.paths.map((p: OverlayBase) => OverlayState.toData(p)), }, null, 2); } static save(overlayState: OverlayState): void { localStorage.setItem("overlay_state", OverlayState.export(overlayState)); } static clear(overlayState: OverlayState, map: L.Map): OverlayState { overlayState.markers.forEach((m: Marker) => m.remove(map)); overlayState.circles.forEach((c: Circle) => c.remove(map)); overlayState.paths.forEach((p: Polygon) => p.remove(map)); const self = new OverlayState(); self.polyline.add(map); return self; } private static toData(source: OverlayBase): OverlayData { let type = OverlayType.POINT; if (source.points.length > 1) { if (source.options.closed) { type = OverlayType.POLYGON; } else { type = OverlayType.POLYLINE; } } else if (source.options.radius) { type = OverlayType.CIRCLE; } return new OverlayData(type, source.name, source.desc, source.points, source.options); } private static fromData(data: OverlayData): OverlayBase { switch(data.type) { case OverlayType.POINT: default: return new Marker(data.name, data.desc, data.points[0], data.options); case OverlayType.CIRCLE: return new Circle(data.name, data.desc, data.points[0], data.options); case OverlayType.POLYGON: case OverlayType.POLYLINE: return new Polygon(data.name, data.desc, data.points, data.options); } } static importWrapper(data: string, overlayState: OverlayState, map: L.Map): boolean { try { const singleData = JSON.parse(data); const overlay = OverlayState.fromData(singleData); switch (singleData.type) { case OverlayType.POINT: overlayState.markers.push(overlay); break; case OverlayType.CIRCLE: overlayState.circles.push(overlay); break; case OverlayType.POLYGON: case OverlayType.POLYLINE: overlayState.paths.push(overlay); break; } overlay.add(map); return true; } catch {} try { const self = OverlayState.import(data); self.markers.forEach((m: Marker) => { overlayState.markers.push(m); m.add(map); }); self.circles.forEach((c: Circle) => { overlayState.circles.push(c); c.add(map); }); self.paths.forEach((p: Polygon) => { overlayState.paths.push(p); p.add(map); }); return true; } catch {} return false; } }