Commit d06a68ec6652741effa395633fb202ce34163a51
1 parent
2b6c492d4d
Exists in
master
and in
1 other branch
add Leaflet Kotlin API
Showing 14 changed files with 879 additions and 0 deletions Inline Diff
- src/main/kotlin/map/Circle.kt
- src/main/kotlin/map/ColorMarker.kt
- src/main/kotlin/map/ControlPosition.kt
- src/main/kotlin/map/LatLong.kt
- src/main/kotlin/map/LeafletMapView.kt
- src/main/kotlin/map/MapConfig.kt
- src/main/kotlin/map/MapLayer.kt
- src/main/kotlin/map/Marker.kt
- src/main/kotlin/map/ScaleControlConfig.kt
- src/main/kotlin/map/Zone.kt
- src/main/kotlin/map/ZoomControlConfig.kt
- src/main/kotlin/map/events/MapClickEvent.kt
- src/main/kotlin/map/events/MapMoveEvent.kt
- src/main/kotlin/map/events/MarkerClickEvent.kt
src/main/kotlin/map/Circle.kt
View file @
d06a68e
File was created | 1 | package fdit.leafletmap | ||
2 | ||||
3 | import javafx.scene.paint.Color | |||
4 | ||||
5 | class Circle private constructor(private var center: LatLong, private var title: String, private var zIndexOffset: Int) { | |||
6 | private var color = Color(0.0, 0.0, 0.0, 0.0) | |||
7 | private lateinit var map: LeafletMapView | |||
8 | private var isAttached = false | |||
9 | private var isDisplayed = false | |||
10 | private var radius = 0.0 | |||
11 | ||||
12 | constructor(position: LatLong, radius: Double, title: String, color: Color, zIndexOffset: Int) : this(position, title, zIndexOffset) { | |||
13 | this.color = color | |||
14 | this.title = title.replace("-", "") | |||
15 | this.center = position | |||
16 | this.radius = nauticalMilesToMeter(radius) | |||
17 | } | |||
18 | ||||
19 | internal fun addToMap(map: LeafletMapView) { | |||
20 | this.map = map | |||
21 | if (map.execScript("typeof circle$title == 'undefined'") as Boolean) { | |||
22 | map.execScript("var circle$title;") | |||
23 | } | |||
24 | if (!this.isAttached) { | |||
25 | val hexColor = "%02x".format((color.red * 255).toInt()) + "%02x".format((color.green * 255).toInt()) + "%02x".format((color.blue * 255).toInt()) | |||
26 | map.execScript("circle$title = L.circle([${center.latitude}, ${center.longitude}], $radius, {color:'#$hexColor'}).addTo(myMap);") | |||
27 | this.isAttached = true | |||
28 | this.isDisplayed = true | |||
29 | } else if (!this.isDisplayed) { | |||
30 | map.execScript("circle$title.addTo(myMap)") | |||
31 | this.isDisplayed = true | |||
32 | } | |||
33 | } | |||
34 | ||||
35 | fun modifyCircle(latLong: LatLong, radius: Double) { | |||
36 | this.center = latLong | |||
37 | this.radius = radius | |||
38 | this.radius = nauticalMilesToMeter(radius) | |||
39 | } | |||
40 | ||||
41 | fun uppdateMap() { | |||
42 | if (this.isAttached && !this.isDisplayed) { | |||
43 | map.execScript("myMap.removeLayer(circle$title);" + | |||
44 | "circle$title = L.circle([${center.latitude}, ${center.longitude}], $radius).addTo(myMap);") | |||
45 | this.isDisplayed = true | |||
46 | } | |||
47 | } | |||
48 | ||||
49 | internal fun removeCircle(map: LeafletMapView) { | |||
50 | if (this.isAttached && this.isDisplayed) { | |||
51 | map.execScript("myMap.removeLayer(circle$title);") |
src/main/kotlin/map/ColorMarker.kt
View file @
d06a68e
File was created | 1 | package fdit.leafletmap | ||
2 | ||||
3 | /** | |||
4 | * Enumeration for all marker colors of the leaflet-color-markers JavaScript library. | |||
5 | * | |||
6 | * @author Stefan Saring | |||
7 | */ | |||
8 | enum class ColorMarker(val iconName: String) { | |||
9 | ||||
10 | BLUE_MARKER("blueIcon"), | |||
11 | RED_MARKER("redIcon"), | |||
12 | GREEN_MARKER("greenIcon"), | |||
13 | ORANGE_MARKER("orangeIcon"), | |||
14 | YELLOW_MARKER("yellowIcon"), | |||
15 | VIOLET_MARKER("violetIcon"), | |||
16 | GREY_MARKER("greyIcon"), |
src/main/kotlin/map/ControlPosition.kt
View file @
d06a68e
File was created | 1 | package fdit.leafletmap | ||
2 | ||||
3 | /** | |||
4 | * Enumeration for all possible map control positions. | |||
5 | * | |||
6 | * @author Stefan Saring | |||
7 | */ | |||
8 | enum class ControlPosition(val positionName: String) { | |||
9 | ||||
10 | TOP_LEFT("topleft"), | |||
11 | TOP_RIGHT("topright"), | |||
12 | BOTTOM_LEFT("bottomleft"), |
src/main/kotlin/map/LatLong.kt
View file @
d06a68e
File was created | 1 | package fdit.leafletmap | ||
2 | ||||
3 | /** | |||
4 | * Immutable value class for defining a geo position. | |||
5 | * | |||
6 | * @author Stefan Saring | |||
7 | */ |
src/main/kotlin/map/LeafletMapView.kt
View file @
d06a68e
File was created | 1 | package fdit.leafletmap | ||
2 | ||||
3 | import fdit.leafletmap.events.* | |||
4 | import fdit.leafletmap.events.MapClickEventMaker | |||
5 | import fdit.leafletmap.events.MapMoveEventMaker | |||
6 | import fdit.leafletmap.events.MarkerClickEventMaker | |||
7 | import javafx.concurrent.Worker | |||
8 | import javafx.scene.layout.StackPane | |||
9 | import javafx.scene.paint.Color | |||
10 | import javafx.scene.shape.Polygon | |||
11 | import javafx.scene.web.WebEngine | |||
12 | import javafx.scene.web.WebView | |||
13 | import fdit.leafletmap.events.* | |||
14 | import netscape.javascript.JSObject | |||
15 | import java.io.ByteArrayOutputStream | |||
16 | import java.io.File | |||
17 | import java.io.IOException | |||
18 | import java.net.URL | |||
19 | import java.util.* | |||
20 | import java.util.concurrent.CompletableFuture | |||
21 | import javax.imageio.ImageIO | |||
22 | ||||
23 | ||||
24 | /** | |||
25 | * JavaFX component for displaying OpenStreetMap based maps by using the Leaflet.js JavaScript library inside a WebView | |||
26 | * browser component.<br/> | |||
27 | * This component can be embedded most easily by placing it inside a StackPane, the component uses then the size of the | |||
28 | * parent automatically. | |||
29 | * | |||
30 | * @author Stefan Saring | |||
31 | * @author Niklas Kellner | |||
32 | */ | |||
33 | class LeafletMapView : StackPane() { | |||
34 | ||||
35 | private val webView = WebView() | |||
36 | private val webEngine: WebEngine = webView.engine | |||
37 | ||||
38 | private var varNameSuffix: Int = 1 | |||
39 | private val mapClickEvent = MapClickEventMaker() | |||
40 | private val markerClickEvent = MarkerClickEventMaker() | |||
41 | private val mapMoveEvent = MapMoveEventMaker() | |||
42 | internal val zoomLimitSmallMarker = 8 | |||
43 | ||||
44 | /** | |||
45 | * Creates the LeafletMapView component, it does not show any map yet. | |||
46 | */ | |||
47 | init { | |||
48 | this.children.add(webView) | |||
49 | } | |||
50 | ||||
51 | /** | |||
52 | * Displays the initial map in the web view. Needs to be called and complete before adding any markers or tracks. | |||
53 | * The returned CompletableFuture will provide the final map load state, the map can be used when the load has | |||
54 | * completed with state SUCCEEDED (use CompletableFuture#whenComplete() for waiting to complete). | |||
55 | * | |||
56 | * @param mapConfig configuration of the map layers and controls | |||
57 | * @return the CompletableFuture which will provide the final map load state | |||
58 | */ | |||
59 | fun displayMap(mapConfig: MapConfig): CompletableFuture<Worker.State> { | |||
60 | val finalMapLoadState = CompletableFuture<Worker.State>() | |||
61 | ||||
62 | webEngine.loadWorker.stateProperty().addListener { _, _, newValue -> | |||
63 | ||||
64 | if (newValue == Worker.State.SUCCEEDED) { | |||
65 | executeMapSetupScripts(mapConfig) | |||
66 | } | |||
67 | ||||
68 | if (newValue == Worker.State.SUCCEEDED || newValue == Worker.State.FAILED) { | |||
69 | finalMapLoadState.complete(newValue) | |||
70 | } | |||
71 | } | |||
72 | ||||
73 | val localFileUrl: URL = LeafletMapView::class.java.getResource("/leafletmap/leafletmap.html") | |||
74 | webEngine.load(localFileUrl.toExternalForm()) | |||
75 | return finalMapLoadState | |||
76 | } | |||
77 | ||||
78 | private fun executeMapSetupScripts(mapConfig: MapConfig) { | |||
79 | ||||
80 | // execute scripts for layer definition | |||
81 | mapConfig.layers.forEachIndexed { i, layer -> | |||
82 | execScript("var layer${i + 1} = ${layer.javaScriptCode};") | |||
83 | } | |||
84 | ||||
85 | val jsLayers = mapConfig.layers | |||
86 | .mapIndexed { i, layer -> "'${layer.displayName}': layer${i + 1}" } | |||
87 | .joinToString(", ") | |||
88 | execScript("var baseMaps = { $jsLayers };") | |||
89 | ||||
90 | // execute script for map view creation (Leaflet attribution must not be a clickable link) | |||
91 | execScript(""" | |||
92 | |var myMap = L.map('map', { | |||
93 | | center: new L.LatLng(${mapConfig.initialCenter.latitude}, ${mapConfig.initialCenter.longitude}), | |||
94 | | zoom: 5, | |||
95 | | zoomControl: false, | |||
96 | | layers: [layer1] | |||
97 | |}); | |||
98 | | | |||
99 | |var markersGroup = L.featureGroup(); | |||
100 | |myMap.addLayer(markersGroup); | |||
101 | |var trackGroup = L.featureGroup(); | |||
102 | |myMap.addLayer(trackGroup); | |||
103 | | | |||
104 | |myMap.addEventListener("contextmenu", function(e){}); | |||
105 | |var attribution = myMap.attributionControl; | |||
106 | |attribution.setPrefix('Leaflet');""".trimMargin()) | |||
107 | ||||
108 | eventZoomChangeIcon() | |||
109 | ||||
110 | // execute script for layer control definition if there are multiple layers | |||
111 | if (mapConfig.layers.size > 1) { | |||
112 | execScript(""" | |||
113 | |var overlayMaps = {}; | |||
114 | |L.control.layers(baseMaps, overlayMaps).addTo(myMap);""".trimMargin()) | |||
115 | ||||
116 | } | |||
117 | ||||
118 | // execute script for scale control definition | |||
119 | if (mapConfig.scaleControlConfig.show) { | |||
120 | execScript("L.control.scale({position: '${mapConfig.scaleControlConfig.position.positionName}', " + | |||
121 | "metric: ${mapConfig.scaleControlConfig.metric}, " + | |||
122 | "imperial: ${!mapConfig.scaleControlConfig.metric}})" + | |||
123 | ".addTo(myMap);") | |||
124 | } | |||
125 | ||||
126 | // execute script for zoom control definition | |||
127 | if (mapConfig.zoomControlConfig.show) { | |||
128 | execScript("L.control.zoom({position: '${mapConfig.zoomControlConfig.position.positionName}'})" + | |||
129 | ".addTo(myMap);") | |||
130 | } | |||
131 | } | |||
132 | ||||
133 | /** | |||
134 | * Sets the view of the map to the specified geographical center position and zoom level. | |||
135 | * | |||
136 | * @param position map center position | |||
137 | * @param zoomLevel zoom level (0 - 19 for OpenStreetMap) | |||
138 | */ | |||
139 | fun setView(position: LatLong, zoomLevel: Int) = | |||
140 | execScript("myMap.setView([${position.latitude}, ${position.longitude}], $zoomLevel);") | |||
141 | ||||
142 | /** | |||
143 | * Pans the map to the specified geographical center position. | |||
144 | * | |||
145 | * @param position map center position | |||
146 | */ | |||
147 | fun panTo(position: LatLong) = | |||
148 | execScript("myMap.panTo([${position.latitude}, ${position.longitude}]);") | |||
149 | ||||
150 | /** | |||
151 | * Sets the zoom of the map to the specified level. | |||
152 | * | |||
153 | * @param zoomLevel zoom level (0 - 19 for OpenStreetMap) | |||
154 | */ | |||
155 | fun setZoom(zoomLevel: Int) = | |||
156 | execScript("myMap.setZoom([$zoomLevel]);") | |||
157 | ||||
158 | /** | |||
159 | * Adds a Marker Object to a map | |||
160 | * | |||
161 | * @param marker the Marker Object | |||
162 | */ | |||
163 | fun addMarker(marker: Marker) { | |||
164 | marker.addToMap(getNextMarkerName(), this) | |||
165 | } | |||
166 | ||||
167 | fun addCircle(circle: Circle) { | |||
168 | circle.addToMap(this) | |||
169 | } | |||
170 | ||||
171 | fun addZone(zone: Zone) { | |||
172 | zone.addToMap(this) | |||
173 | } | |||
174 | ||||
175 | /** | |||
176 | * Removes an existing marker from the map | |||
177 | * | |||
178 | * @param marker the Marker object | |||
179 | */ | |||
180 | fun removeMarker(marker: Marker) { | |||
181 | execScript("myMap.removeLayer(${marker.getName()});") | |||
182 | } | |||
183 | ||||
184 | fun removeCircle(circle: Circle) { | |||
185 | circle.removeCircle(this) | |||
186 | } | |||
187 | ||||
188 | fun removeZone(zone: Zone) { | |||
189 | zone.removeZone() | |||
190 | } | |||
191 | ||||
192 | fun removeZone(id: String) { | |||
193 | val idSanitized = id.replace("-", "") | |||
194 | execScript("myMap.removeLayer(polygon$idSanitized);") | |||
195 | } | |||
196 | ||||
197 | ||||
198 | fun uppdateCircle(circle: Circle, latLong: LatLong, radius: Double) { | |||
199 | circle.modifyCircle(latLong, radius) | |||
200 | circle.uppdateMap() | |||
201 | } | |||
202 | ||||
203 | fun setEventMousePosition() { | |||
204 | execScript("var lat=0.0, lng=0.0;\n" + | |||
205 | "myMap.addEventListener('mousemove', function(ev) {\n" + | |||
206 | " lat = ev.latlng.lat;\n" + | |||
207 | " lng = ev.latlng.lng;\n" + | |||
208 | "});" | |||
209 | ) | |||
210 | } | |||
211 | ||||
212 | fun getMousePosition(): LatLong { | |||
213 | val lat = execScript("lat;") as Double | |||
214 | val lng = execScript("lng;") as Double | |||
215 | return LatLong(lat, lng) | |||
216 | } | |||
217 | ||||
218 | /** | |||
219 | * Adds a custom marker type | |||
220 | * | |||
221 | * @param markerName the name of the marker type | |||
222 | * @param iconUrl the url if the marker icon | |||
223 | */ | |||
224 | fun addCustomMarker(markerName: String, iconUrl: String): String { | |||
225 | execScript("var $markerName = L.icon({\n" + | |||
226 | "iconUrl: '${createImage(iconUrl, "png")}',\n" + | |||
227 | "iconSize: [24, 24],\n" + | |||
228 | "iconAnchor: [12, 12],\n" + | |||
229 | "});") | |||
230 | return markerName | |||
231 | } | |||
232 | ||||
233 | private fun createImage(path: String, type: String): String { | |||
234 | val image = ImageIO.read(File(path)) | |||
235 | var imageString: String? = null | |||
236 | val bos = ByteArrayOutputStream() | |||
237 | ||||
238 | try { | |||
239 | ImageIO.write(image, type, bos) | |||
240 | val imageBytes = bos.toByteArray() | |||
241 | ||||
242 | val encoder = Base64.getEncoder() | |||
243 | imageString = encoder.encodeToString(imageBytes) | |||
244 | ||||
245 | bos.close() | |||
246 | } catch (e: IOException) { | |||
247 | e.printStackTrace() | |||
248 | } | |||
249 | return "data:image/$type;base64,$imageString" | |||
250 | } | |||
251 | ||||
252 | /** | |||
253 | * Sets the onMarkerClickListener | |||
254 | * | |||
255 | * @param listener the onMarerClickEventListener | |||
256 | */ | |||
257 | fun onMarkerClick(listener: MarkerClickEventListener) { | |||
258 | val win = execScript("document") as JSObject | |||
259 | win.setMember("java", this) | |||
260 | markerClickEvent.addListener(listener) | |||
261 | } | |||
262 | ||||
263 | /** | |||
264 | * Handles the callback from the markerClickEvent | |||
265 | */ | |||
266 | fun markerClick(title: String) { | |||
267 | markerClickEvent.MarkerClickEvent(title) | |||
268 | } | |||
269 | ||||
270 | /** | |||
271 | * Sets the onMapMoveListener | |||
272 | * | |||
273 | * @param listener the MapMoveEventListener | |||
274 | */ | |||
275 | fun onMapMove(listener: MapMoveEventListener) { | |||
276 | val win = execScript("document") as JSObject | |||
277 | win.setMember("java", this) | |||
278 | execScript("myMap.on('moveend', function(e){ document.java.mapMove(myMap.getCenter().lat, myMap.getCenter().lng);});") | |||
279 | mapMoveEvent.addListener(listener) | |||
280 | } | |||
281 | ||||
282 | /** | |||
283 | * Handles the callback from the mapMoveEvent | |||
284 | */ | |||
285 | fun mapMove(lat: Double, lng: Double) { | |||
286 | val latlng = LatLong(lat, lng) | |||
287 | mapMoveEvent.MapMoveEvent(latlng) | |||
288 | } | |||
289 | ||||
290 | /** | |||
291 | * Sets the onMapClickListener | |||
292 | * | |||
293 | * @param listener the onMapClickEventListener | |||
294 | */ | |||
295 | fun onMapClick(listener: MapClickEventListener) { | |||
296 | val win = execScript("document") as JSObject | |||
297 | win.setMember("java", this) | |||
298 | execScript("myMap.on('click', function(e){ document.java.mapClick(e.latlng.lat, e.latlng.lng);});") | |||
299 | mapClickEvent.addListener(listener) | |||
300 | } | |||
301 | ||||
302 | /** | |||
303 | * Handles the callback from the mapClickEvent | |||
304 | */ | |||
305 | fun mapClick(lat: Double, lng: Double) { | |||
306 | val latlng = LatLong(lat, lng) | |||
307 | mapClickEvent.MapClickEvent(latlng) | |||
308 | } | |||
309 | ||||
310 | /** | |||
311 | * Draws a track path along the specified positions. | |||
312 | * | |||
313 | * @param positions list of track positions | |||
314 | */ | |||
315 | fun addTrack(positions: List<LatLong>) { | |||
316 | ||||
317 | val jsPositions = positions | |||
318 | .map { " [${it.latitude}, ${it.longitude}]" } | |||
319 | .joinToString(", \n") | |||
320 | ||||
321 | execScript(""" | |||
322 | |var latLngs = [ | |||
323 | |$jsPositions | |||
324 | |]; | |||
325 | |var polyline = L.polyline(latLngs, {color: 'red', weight: 2}).addTo(myMap);""".trimMargin()) | |||
326 | } | |||
327 | ||||
328 | fun addTrack(positions: List<LatLong>, id: String, color: Color, tooltip: String) { | |||
329 | ||||
330 | val jsPositions = positions | |||
331 | .map { " [${it.latitude}, ${it.longitude}]" } | |||
332 | .joinToString(", \n") | |||
333 | ||||
334 | val cleanTooltip = tooltip.replace("'", "'") | |||
335 | execScript(""" | |||
336 | |var latLngs = [ | |||
337 | |$jsPositions | |||
338 | |]; | |||
339 | |var color = "rgb(${Math.floor(color.getRed() * 255).toInt()} ,${Math.floor(color.getGreen() * 255).toInt()},${Math.floor(color.getBlue() * 255).toInt()})"; | |||
340 | |var polyline$id = L.polyline(latLngs, {color: color, weight: 2, zIndexOffset: 200}).bindTooltip('$cleanTooltip', {sticky: true}).addTo(trackGroup)""".trimMargin()) | |||
341 | } | |||
342 | ||||
343 | fun makeVesselTrackTransparent(id: String) { | |||
344 | execScript("polyline$id.setStyle({opacity: 0.5});") | |||
345 | } | |||
346 | ||||
347 | fun highlightTrack(id: String) { | |||
348 | execScript("polyline$id.setStyle({weight: 4});") |
src/main/kotlin/map/MapConfig.kt
View file @
d06a68e
File was created | 1 | package fdit.leafletmap | ||
2 | ||||
3 | /** | |||
4 | * Class for defining the layers and controls in the map to be shown. | |||
5 | * | |||
6 | * @property layers List of layers to be shown in the map, the default layer is OpenStreetMap. If more than one layer is | |||
7 | * specified, then a layer selection control will be shown in the top right corner. | |||
8 | * @property zoomControlConfig Zoom control definition, by default it's shown in the top left corner. | |||
9 | * @property scaleControlConfig Scale control definition, by default it's not shown. | |||
10 | * @property initialCenter Initial center position of the map (default is London city). | |||
11 | * | |||
12 | * @author Stefan Saring | |||
13 | */ | |||
14 | class MapConfig @JvmOverloads constructor( | |||
15 | ||||
16 | val layers: List<MapLayer> = listOf(MapLayer.OPENSTREETMAP), | |||
17 | val zoomControlConfig: ZoomControlConfig = ZoomControlConfig(), | |||
18 | val scaleControlConfig: ScaleControlConfig = ScaleControlConfig(), |
src/main/kotlin/map/MapLayer.kt
View file @
d06a68e
File was created | 1 | package fdit.leafletmap | ||
2 | ||||
3 | /** | |||
4 | * Enumeration for all supported map layers. | |||
5 | * | |||
6 | * @author Stefan Saring | |||
7 | */ | |||
8 | enum class MapLayer(val displayName: String, val javaScriptCode: String) { | |||
9 | ||||
10 | /** OpenStreetMap layer. */ | |||
11 | OPENSTREETMAP("OpenStreetMap", """ | |||
12 | L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { | |||
13 | attribution: 'Map data © OpenStreetMap and contributors', noWrap: true | |||
14 | })"""), | |||
15 | ||||
16 | /** OpenCycleMap layer. */ | |||
17 | OPENCYCLEMAP("OpenCycleMap", """ | |||
18 | L.tileLayer('http://{s}.tile.opencyclemap.org/cycle/{z}/{x}/{y}.png', { | |||
19 | attribution: '© OpenCycleMap, Map data © OpenStreetMap contributors', noWrap: true | |||
20 | })"""), | |||
21 | ||||
22 | /** Hike & bike maps layer (HikeBikeMap.org). */ | |||
23 | HIKE_BIKE_MAP("Hike & Bike Map", """ | |||
24 | L.tileLayer('http://{s}.tiles.wmflabs.org/hikebike/{z}/{x}/{y}.png', { | |||
25 | attribution: '© HikeBikeMap.org, Map data © OpenStreetMap and contributors', noWrap: true | |||
26 | })"""), | |||
27 | ||||
28 | /** MTB map (mtbmap.cz). */ | |||
29 | MTB_MAP("MTB Map", """ | |||
30 | L.tileLayer('http://tile.mtbmap.cz/mtbmap_tiles/{z}/{x}/{y}.png', { | |||
31 | attribution: '© OpenStreetMap and USGS', noWrap: true | |||
32 | })"""), | |||
33 | ||||
34 | /** MapBox layer in streets mode (consider: a project specific access token is required!). */ |
src/main/kotlin/map/Marker.kt
View file @
d06a68e
File was created | 1 | package fdit.leafletmap | ||
2 | ||||
3 | import fdit.gui.graphicalScenarioEditor.GraphicalScenarioEditorContext | |||
4 | import fdit.gui.utils.tooltip.VesselTooltipUtils.formatVesselSnapshotTooltip | |||
5 | import fdit.metamodel.vessel.Vessel | |||
6 | ||||
7 | /** | |||
8 | * Creates a marker at the specified geographical position. | |||
9 | * | |||
10 | * @author Niklas Kellner | |||
11 | * | |||
12 | * @param position marker position | |||
13 | * @param title marker title shown in tooltip (pass empty string when tooltip not needed) | |||
14 | * @param zIndexOffset zIndexOffset (higher number means on top) | |||
15 | * | |||
16 | */ | |||
17 | class Marker private constructor(private var position: LatLong, private var zIndexOffset: Int) { | |||
18 | private var marker = "aircraftIcon" | |||
19 | private var markerSmall = "aircraftSmallIcon" | |||
20 | private lateinit var map: LeafletMapView | |||
21 | private var attached = false | |||
22 | private var clickable = false | |||
23 | private var name = "" | |||
24 | private var tooltip = "" | |||
25 | private var rotation = 0 | |||
26 | private lateinit var aircraft: Vessel | |||
27 | private lateinit var context: GraphicalScenarioEditorContext | |||
28 | private var relativeDate: Double = 0.0 | |||
29 | ||||
30 | ||||
31 | constructor(position: LatLong, aircraft: Vessel, relativeDate: Double, context: GraphicalScenarioEditorContext, aircraftIcon: String, zIndexOffset: Int) : this(position, zIndexOffset){ | |||
32 | this.aircraft = aircraft | |||
33 | this.context = context | |||
34 | this.relativeDate = relativeDate | |||
35 | this.marker = aircraftIcon | |||
36 | } | |||
37 | ||||
38 | /** | |||
39 | * Adds the marker to a map, gets called from the mapAddMarker | |||
40 | * | |||
41 | * @param nextMarkerName the variable name of the marker | |||
42 | * @param map the LeafetMapView | |||
43 | */ | |||
44 | internal fun addToMap(nextMarkerName: String, map: LeafletMapView) { | |||
45 | this.name = nextMarkerName | |||
46 | this.map = map | |||
47 | this.attached = true | |||
48 | map.execScript(""" | |||
49 | |var currentZoom = myMap.getZoom(); | |||
50 | |var $name; | |||
51 | |if (currentZoom < ${map.zoomLimitSmallMarker}) { | |||
52 | |$name = L.marker([${position.latitude}, ${position.longitude}], {title: '', icon: $markerSmall, zIndexOffset: $zIndexOffset}).addTo(markersGroup); | |||
53 | |} else { | |||
54 | |$name = L.marker([${position.latitude}, ${position.longitude}], {title: '', icon: $marker, zIndexOffset: $zIndexOffset}).addTo(markersGroup); | |||
55 | |} | |||
56 | """.trimMargin()) | |||
57 | setTooltip() | |||
58 | if (clickable) { | |||
59 | setClickable() | |||
60 | } | |||
61 | } | |||
62 | ||||
63 | fun setTooltip() { | |||
64 | this.tooltip = formatVesselSnapshotTooltip(aircraft, | |||
65 | context.getGraphicalScenario().getRecording(), | |||
66 | relativeDate) | |||
67 | this.tooltip = tooltip.replace("\n", "<br>") | |||
68 | this.tooltip = tooltip.replace("'", "'") | |||
69 | map.execScript("$name.bindTooltip('<div id=\"html_c92f9552ec164f36978869550cb44ffe\" style=\"width: 100.0%; height: 100.0%;\">${this.tooltip}</div>');") | |||
70 | } | |||
71 | ||||
72 | ||||
73 | /** | |||
74 | * Changes the icon of the marker | |||
75 | * | |||
76 | * @param newIcon the name of the new icon | |||
77 | */ | |||
78 | fun changeIcon(newIcon: String) { | |||
79 | this.marker = newIcon | |||
80 | if (attached) { | |||
81 | map.execScript("$name.setIcon($marker);") | |||
82 | } | |||
83 | } | |||
84 | ||||
85 | /** | |||
86 | * Changes the icon of the marker | |||
87 | * | |||
88 | * @param newIcon the new ColorMarker | |||
89 | */ | |||
90 | fun changeIcon(newIcon: ColorMarker) { | |||
91 | this.marker = newIcon.iconName | |||
92 | if (attached) { | |||
93 | map.execScript("$name.setIcon(${newIcon.iconName});") | |||
94 | } | |||
95 | } | |||
96 | ||||
97 | /** | |||
98 | * Moves the existing marker specified by the variable name to the new geographical position. | |||
99 | * | |||
100 | * @param position new marker position | |||
101 | */ | |||
102 | fun move(position: LatLong) { | |||
103 | this.position = position | |||
104 | if (attached) { | |||
105 | map.execScript("$name.setLatLng([${this.position.latitude}, ${this.position.longitude}]);") | |||
106 | setTooltip() | |||
107 | } | |||
108 | } | |||
109 | ||||
110 | fun move(position: LatLong, aircraft: Vessel, relativeDate: Double) { | |||
111 | this.aircraft = aircraft | |||
112 | this.relativeDate = relativeDate | |||
113 | this.position = position | |||
114 | if (attached) { | |||
115 | map.execScript("$name.setLatLng([${this.position.latitude}, ${this.position.longitude}]);") | |||
116 | } | |||
117 | } | |||
118 | ||||
119 | fun setRotation(rotation: Int) { | |||
120 | if (rotation > 360 || rotation < 0) { | |||
121 | this.rotation = 0 | |||
122 | } else { | |||
123 | this.rotation = rotation | |||
124 | } | |||
125 | if (attached) { | |||
126 | map.execScript("$name.setRotationAngle(${this.rotation})") | |||
127 | } |
src/main/kotlin/map/ScaleControlConfig.kt
View file @
d06a68e
File was created | 1 | package fdit.leafletmap | ||
2 | ||||
3 | /** | |||
4 | * Class for defining the scale control of the map. The scale can show either metric or imperial units. | |||
5 | ||||
6 | * @author Stefan Saring | |||
7 | */ | |||
8 | class ScaleControlConfig @JvmOverloads constructor( | |||
9 | val show: Boolean = false, |
src/main/kotlin/map/Zone.kt
View file @
d06a68e
File was created | 1 | package fdit.leafletmap | ||
2 | ||||
3 | class Zone constructor(private var title: String) { | |||
4 | private lateinit var map: LeafletMapView | |||
5 | private var isAttached = false | |||
6 | private var isDisplayed = false | |||
7 | private var positions = listOf<LatLong>() | |||
8 | ||||
9 | ||||
10 | fun addToMap(map: LeafletMapView) { | |||
11 | this.map = map | |||
12 | ||||
13 | if (map.execScript("typeof zone$title == 'undefined';") as Boolean) { | |||
14 | map.execScript("var zone$title") | |||
15 | } | |||
16 | if (!this.isAttached) { | |||
17 | ||||
18 | map.execScript("var points$title = [];" + | |||
19 | "zone$title = L.polygon(points$title).addTo(myMap);") | |||
20 | this.isAttached = true | |||
21 | this.isDisplayed = true | |||
22 | } else if (!this.isDisplayed) { | |||
23 | map.execScript("zone$title.addTo(myMap);") | |||
24 | this.isDisplayed = true | |||
25 | } | |||
26 | } | |||
27 | ||||
28 | private fun addPoint(latLong: LatLong) { | |||
29 | map.execScript("points$title.push([${latLong.latitude}, ${latLong.longitude}]);") | |||
30 | } | |||
31 | ||||
32 | fun updatePoints(positions: List<LatLong>) { | |||
33 | this.positions = positions | |||
34 | if (map.execScript("typeof points$title == 'undefined'") as Boolean) { | |||
35 | map.execScript("var points$title = [];") | |||
36 | } else { | |||
37 | map.execScript("points$title = [];") | |||
38 | } | |||
39 | for (position in positions) { | |||
40 | addPoint(position) | |||
41 | } | |||
42 | } | |||
43 | ||||
44 | fun updateMap() { | |||
45 | if (this.isAttached) { | |||
46 | map.execScript("myMap.removeLayer(zone$title);" + | |||
47 | "zone$title = L.polygon(points$title).addTo(myMap);") | |||
48 | this.isDisplayed = true | |||
49 | } |
src/main/kotlin/map/ZoomControlConfig.kt
View file @
d06a68e
File was created | 1 | package fdit.leafletmap | ||
2 | ||||
3 | /** | |||
4 | * Class for defining the zoom control of the map. | |||
5 | ||||
6 | * @author Stefan Saring | |||
7 | */ | |||
8 | class ZoomControlConfig @JvmOverloads constructor( |
src/main/kotlin/map/events/MapClickEvent.kt
View file @
d06a68e
File was created | 1 | package fdit.leafletmap.events | ||
2 | ||||
3 | import fdit.leafletmap.LatLong | |||
4 | import java.util.* | |||
5 | ||||
6 | /** | |||
7 | * Handles the MapClickEvent | |||
8 | * @author Niklas Kellner | |||
9 | */ | |||
10 | interface MapClickEventListener { | |||
11 | fun onMapClick(latLong: LatLong) | |||
12 | } | |||
13 | ||||
14 | internal class MapClickEventMaker { | |||
15 | private val listeners = ArrayList<MapClickEventListener>() | |||
16 | ||||
17 | fun addListener(toAdd: MapClickEventListener) { | |||
18 | listeners.add(toAdd) | |||
19 | } | |||
20 | ||||
21 | fun MapClickEvent(latLong: LatLong) { |
src/main/kotlin/map/events/MapMoveEvent.kt
View file @
d06a68e
File was created | 1 | package fdit.leafletmap.events | ||
2 | ||||
3 | import fdit.leafletmap.LatLong | |||
4 | import java.util.* | |||
5 | ||||
6 | /** | |||
7 | * Handles the MapMoveEvent | |||
8 | * | |||
9 | * @author Niklas Kellner | |||
10 | */ | |||
11 | interface MapMoveEventListener { | |||
12 | fun onMapMove(center: LatLong) | |||
13 | } | |||
14 | ||||
15 | internal class MapMoveEventMaker { | |||
16 | private val listeners = ArrayList<MapMoveEventListener>() | |||
17 | ||||
18 | fun addListener(toAdd: MapMoveEventListener) { | |||
19 | listeners.add(toAdd) | |||
20 | } | |||
21 | ||||
22 | fun MapMoveEvent(latLong: LatLong) { |
src/main/kotlin/map/events/MarkerClickEvent.kt
View file @
d06a68e
File was created | 1 | package fdit.leafletmap.events | ||
2 | ||||
3 | import java.util.* | |||
4 | ||||
5 | /** | |||
6 | * Handles the MarkerClickEvent | |||
7 | * | |||
8 | * @author Niklas Kellner | |||
9 | */ | |||
10 | interface MarkerClickEventListener { | |||
11 | fun onMarkerClick(title: String) | |||
12 | } | |||
13 | ||||
14 | internal class MarkerClickEventMaker { | |||
15 | private val listeners = ArrayList<MarkerClickEventListener>() | |||
16 | private var listenerSet = false | |||
17 | ||||
18 | fun addListener(toAdd: MarkerClickEventListener) { | |||
19 | listeners.add(toAdd) | |||
20 | listenerSet = true | |||
21 | } | |||
22 | ||||
23 | fun MarkerClickEvent(title: String){ | |||
24 | // Notify everybody that may be interested. | |||
25 | for (hl in listeners) | |||
26 | hl.onMarkerClick(title) |