Commit d06a68ec6652741effa395633fb202ce34163a51

Authored by lsagona
1 parent 2b6c492d4d
Exists in master and in 1 other branch dev

add Leaflet Kotlin API

Showing 14 changed files with 879 additions and 0 deletions Side-by-side Diff

src/main/kotlin/map/Circle.kt View file @ d06a68e
  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);")
  52 + this.isDisplayed = false
  53 + }
  54 + }
  55 +
  56 + private fun nauticalMilesToMeter(nauticalMiles: Double): Double {
  57 + return nauticalMiles * 1.852
  58 + }
  59 +
  60 +}
src/main/kotlin/map/ColorMarker.kt View file @ d06a68e
  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"),
  17 + BLACK_MARKER("blackIcon")
  18 +}
src/main/kotlin/map/ControlPosition.kt View file @ d06a68e
  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"),
  13 + BOTTOM_RIGHT("bottomright")
  14 +}
src/main/kotlin/map/LatLong.kt View file @ d06a68e
  1 +package fdit.leafletmap
  2 +
  3 +/**
  4 + * Immutable value class for defining a geo position.
  5 + *
  6 + * @author Stefan Saring
  7 + */
  8 +data class LatLong(val latitude: Double, val longitude: Double)
src/main/kotlin/map/LeafletMapView.kt View file @ d06a68e
  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("'", "&apos;")
  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});")
  349 + }
  350 +
  351 + fun normalizeVesselTrack(id: String) {
  352 + execScript("polyline$id.setStyle({opacity: 1,weight: 2});")
  353 + }
  354 +
  355 + fun eventZoomChangeIcon() {
  356 + execScript("""
  357 + |myMap.on('zoomend', function() {
  358 + |var currentZoom = myMap.getZoom();
  359 + |if (currentZoom < $zoomLimitSmallMarker) {
  360 + |markersGroup.eachLayer(function(layer) {
  361 + return layer.setIcon(aircraftSmallIcon)
  362 + |});
  363 + |} else {
  364 + |markersGroup.eachLayer(function(layer) {
  365 + return layer.setIcon(aircraftIcon)
  366 + |});
  367 + |}
  368 + |});
  369 + """.trimMargin())
  370 + }
  371 +
  372 + fun removeTrack(id: String) {
  373 + execScript("myMap.removeLayer(polyline$id);")
  374 + }
  375 +
  376 + fun fitBoundsMarkers() {
  377 + execScript("setTimeout(() => {myMap.fitBounds(markersGroup.getBounds().pad(0.05));}, 500);")
  378 + }
  379 +
  380 + fun addZone(polygon: Polygon, id: String, color: Color) {
  381 + val points = polygon.points
  382 + val latLongs = arrayListOf<LatLong>()
  383 + var lat: Double
  384 + var lon = 0.0
  385 +
  386 + for (i in 0 until points.size) {
  387 + if (i % 2 == 0) {
  388 + lon = points[i]
  389 + } else {
  390 + lat = points[i]
  391 + latLongs.add(LatLong(lat, lon))
  392 + }
  393 + }
  394 +
  395 + val jsPositions = latLongs
  396 + .map { " [${it.latitude}, ${it.longitude}]" }
  397 + .joinToString(", \n")
  398 + val idSanitized = id.replace("-", "")
  399 + execScript("""
  400 + |var latLngs = [
  401 + |$jsPositions
  402 + |];
  403 + |var color = "rgb(${Math.floor(color.getRed() * 255).toInt()} ,${Math.floor(color.getGreen() * 255).toInt()},${Math.floor(color.getBlue() * 255).toInt()})";
  404 + |var polygon$idSanitized = L.polygon(latLngs, {color: color}).addTo(myMap);""".trimMargin())
  405 +
  406 + }
  407 +
  408 + internal fun execScript(script: String) = webEngine.executeScript(script)
  409 +
  410 + private fun getNextMarkerName(): String = "marker${varNameSuffix++}"
  411 +}
src/main/kotlin/map/MapConfig.kt View file @ d06a68e
  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(),
  19 + val initialCenter: LatLong = LatLong(0.0, 0.0)
  20 +)
src/main/kotlin/map/MapLayer.kt View file @ d06a68e
  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 &copy; 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: '&copy; OpenCycleMap, Map data &copy; 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: '&copy; HikeBikeMap.org, Map data &copy; 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: '&copy; OpenStreetMap and USGS', noWrap: true
  32 + })"""),
  33 +
  34 + /** MapBox layer in streets mode (consider: a project specific access token is required!). */
  35 + MAPBOX("MapBox", """
  36 + L.tileLayer('https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token=pk.eyJ1IjoibWFwYm94IiwiYSI6ImNpejY4NXVycTA2emYycXBndHRqcmZ3N3gifQ.rJcFIG214AriISLbB6B5aw', {
  37 + id: 'mapbox.streets',
  38 + attribution: 'Map data &copy; OpenStreetMap contributors, Imagery &copy; Mapbox', noWrap: true
  39 + })""")
  40 +}
src/main/kotlin/map/Marker.kt View file @ d06a68e
  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("'", "&apos;")
  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 + }
  128 + }
  129 +
  130 +
  131 + /**
  132 + * Sets the marker clickable
  133 + */
  134 + private fun setClickable() {
  135 + this.clickable = true
  136 + if (attached) {
  137 + map.execScript("$name.on('click', function(e){ document.java.markerClick($name.options.title)})")
  138 + }
  139 + }
  140 +
  141 + internal fun getName(): String = this.name
  142 +}
src/main/kotlin/map/ScaleControlConfig.kt View file @ d06a68e
  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,
  10 + val position: ControlPosition = ControlPosition.BOTTOM_LEFT,
  11 + val metric: Boolean = true)
src/main/kotlin/map/Zone.kt View file @ d06a68e
  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 + }
  50 + }
  51 +
  52 + internal fun removeZone() {
  53 + if (this.isAttached && this.isDisplayed) {
  54 + map.execScript("myMap.removeLayer(zone$title);")
  55 + this.isDisplayed = false
  56 + }
  57 + }
  58 +
  59 +}
src/main/kotlin/map/ZoomControlConfig.kt View file @ d06a68e
  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(
  9 + val show: Boolean = true,
  10 + val position: ControlPosition = ControlPosition.TOP_LEFT)
src/main/kotlin/map/events/MapClickEvent.kt View file @ d06a68e
  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) {
  22 + // Notify everybody that may be interested.
  23 + for (hl in listeners)
  24 + hl.onMapClick(latLong)
  25 + }
  26 +}
src/main/kotlin/map/events/MapMoveEvent.kt View file @ d06a68e
  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) {
  23 + // Notify everybody that may be interested.
  24 + for (hl in listeners)
  25 + hl.onMapMove(latLong)
  26 + }
  27 +}
src/main/kotlin/map/events/MarkerClickEvent.kt View file @ d06a68e
  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)
  27 + }
  28 +
  29 +
  30 + fun isListenerSet(): Boolean{
  31 + return listenerSet
  32 + }
  33 +}