-
Notifications
You must be signed in to change notification settings - Fork 55
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added LeafletMap component as a Maven submodule.
- Loading branch information
Showing
42 changed files
with
11,600 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
## LeafletMap | ||
|
||
LeafletMap is a JavaFX component for displaying an OpenSteetMap based map | ||
inside a JavaFX WebView by using the Leaflet JavaScript library. | ||
|
||
There is a demo application inside the test directory which shows how to | ||
use the LeafletMap component. | ||
|
||
Both the LeafletMap component and the demo application are written in Kotlin. | ||
|
||
|
||
#### Dependencies and used libraries | ||
|
||
* Kotlin 1.0.6 | ||
* JavaSE 8 (tested with 8u121) | ||
* Leaflet 1.0.3 (included) | ||
* Homepage: http://leafletjs.com/ | ||
* License: 2-clause BSD License | ||
* Documentation: http://leafletjs.com/reference-1.0.3.html | ||
* leaflet-color-markers (included, modified) | ||
* Homepage: https://github.com/pointhi/leaflet-color-markers | ||
* License: not specified | ||
* jackson-module-kotlin 2.8.6 (for the demo only, uses Jackson 2.8) | ||
* Homepage: https://github.com/FasterXML/jackson-module-kotlin | ||
* License: not specified | ||
|
||
|
||
#### Status | ||
|
||
* colored markers are supported by the embedded "leaflet-color-markers" library | ||
(modified, e.g. added proper retina icon support) | ||
* the leaflet and the leaflet-color-markers libraries are included locally, no | ||
download at runtime needed | ||
* map viewer features: | ||
* supports OpenSteetMap, OpenCycleMap, HikeBikeMap, MtbMap and MapBox | ||
layers, more can be added easily | ||
* MapBox layer: a project specific token is required for MapBox! A test | ||
token for a limited time can be found in the Leaflet tutorial. | ||
* layers can be switched at runtime by the user | ||
* zoom and scale controls can be configured | ||
* scale supports metric and imperial units | ||
* markers in multiple colors can be displayed | ||
* tracks (routes) can be displayed | ||
* the map is zoomed properly to fit the track | ||
* tooltips can be displayed on the map | ||
* map viewer can be used offline, the route and the markers are shown without | ||
the map data | ||
* the LeafletMapView component API can also be used from Java without problems | ||
(default method parameters are supported via @JvmOverloads) | ||
* the demo application displays a GPS track read from a JSON file, the user can | ||
replay the track by using a position slider | ||
|
||
|
||
#### TODO | ||
|
||
* test memory usage |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<project xmlns="http://maven.apache.org/POM/4.0.0" | ||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||
|
||
<modelVersion>4.0.0</modelVersion> | ||
<groupId>de.saring</groupId> | ||
<artifactId>leafletmap</artifactId> | ||
<name>leafletmap</name> | ||
<packaging>jar</packaging> | ||
<version>1.0.0-SNAPSHOT</version> | ||
|
||
<organization> | ||
<name>Saring</name> | ||
<url>http://www.saring.de</url> | ||
</organization> | ||
|
||
<properties> | ||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | ||
<kotlin.version>1.0.6</kotlin.version> | ||
</properties> | ||
|
||
<dependencies> | ||
<dependency> | ||
<groupId>org.jetbrains.kotlin</groupId> | ||
<artifactId>kotlin-stdlib</artifactId> | ||
<version>${kotlin.version}</version> | ||
</dependency> | ||
<dependency> | ||
<groupId>com.fasterxml.jackson.module</groupId> | ||
<artifactId>jackson-module-kotlin</artifactId> | ||
<version>2.8.6</version> | ||
<scope>test</scope> | ||
</dependency> | ||
</dependencies> | ||
|
||
<build> | ||
<defaultGoal>clean package</defaultGoal> | ||
<sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory> | ||
<testSourceDirectory>${project.basedir}/src/test/kotlin</testSourceDirectory> | ||
<plugins> | ||
<plugin> | ||
<artifactId>kotlin-maven-plugin</artifactId> | ||
<groupId>org.jetbrains.kotlin</groupId> | ||
<version>${kotlin.version}</version> | ||
<executions> | ||
<execution> | ||
<id>compile</id> | ||
<phase>compile</phase> | ||
<goals> | ||
<goal>compile</goal> | ||
</goals> | ||
</execution> | ||
<execution> | ||
<id>test-compile</id> | ||
<phase>test-compile</phase> | ||
<goals> | ||
<goal>test-compile</goal> | ||
</goals> | ||
</execution> | ||
</executions> | ||
</plugin> | ||
</plugins> | ||
</build> | ||
|
||
</project> |
18 changes: 18 additions & 0 deletions
18
leafletmap/src/main/kotlin/de/saring/leafletmap/ColorMarker.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package de.saring.leafletmap | ||
|
||
/** | ||
* Enumeration for all marker colors of the leaflet-color-markers JavaScript library. | ||
* | ||
* @author Stefan Saring | ||
*/ | ||
enum class ColorMarker(val iconName: String) { | ||
|
||
BLUE_MARKER("blueIcon"), | ||
RED_MARKER("redIcon"), | ||
GREEN_MARKER("greenIcon"), | ||
ORANGE_MARKER("orangeIcon"), | ||
YELLOW_MARKER("yellowIcon"), | ||
VIOLET_MARKER("violetIcon"), | ||
GREY_MARKER("greyIcon"), | ||
BLACK_MARKER("blackIcon") | ||
} |
14 changes: 14 additions & 0 deletions
14
leafletmap/src/main/kotlin/de/saring/leafletmap/ControlPosition.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package de.saring.leafletmap | ||
|
||
/** | ||
* Enumeration for all possible map control positions. | ||
* | ||
* @author Stefan Saring | ||
*/ | ||
enum class ControlPosition(val positionName: String) { | ||
|
||
TOP_LEFT("topleft"), | ||
TOP_RIGHT("topright"), | ||
BOTTOM_LEFT("bottomleft"), | ||
BOTTOM_RIGHT("bottomright") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package de.saring.leafletmap | ||
|
||
/** | ||
* Immutable value class for defining a geo position. | ||
* | ||
* @author Stefan Saring | ||
*/ | ||
data class LatLong(val latitude: Double, val longitude: Double) |
153 changes: 153 additions & 0 deletions
153
leafletmap/src/main/kotlin/de/saring/leafletmap/LeafletMapView.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
package de.saring.leafletmap | ||
|
||
import javafx.scene.layout.StackPane | ||
import javafx.scene.web.WebEngine | ||
import javafx.scene.web.WebView | ||
|
||
import java.net.URL | ||
import javafx.concurrent.Worker | ||
|
||
|
||
/** | ||
* JavaFX component for displaying OpenStreetMap based maps by using the Leaflet.js JavaScript library inside a WebView | ||
* browser component.<br/> | ||
* This component can be embedded most easily by placing it inside a StackPane, the component uses then the size of the | ||
* parent automatically. | ||
* | ||
* @author Stefan Saring | ||
*/ | ||
class LeafletMapView : StackPane() { | ||
|
||
private val webView = WebView() | ||
private val webEngine: WebEngine = webView.engine | ||
|
||
private var varNameSuffix: Int = 1 | ||
|
||
/** | ||
* Creates the LeafletMapView component, it does not show any map yet. | ||
*/ | ||
init { | ||
this.children.add(webView) | ||
} | ||
|
||
/** | ||
* Displays the initial map in the web view. Needs to be called and complete before adding any markers or tracks. | ||
* The completionNotifier callback can be used for notification when the map is initially displayed. | ||
* | ||
* @param mapConfig configuration of the map layers and controls | ||
* @param completionNotifier notifies the caller when the map is displayed or the loading has failed (optional) | ||
*/ | ||
@JvmOverloads | ||
fun displayMap(mapConfig: MapConfig, completionNotifier: ((Worker.State) -> Unit)? = null) { | ||
|
||
webEngine.loadWorker.stateProperty().addListener { observable, oldValue, newValue -> | ||
|
||
if (newValue == Worker.State.SUCCEEDED) { | ||
executeMapSetupScripts(mapConfig) | ||
} | ||
|
||
if (completionNotifier != null && (newValue == Worker.State.SUCCEEDED || newValue == Worker.State.FAILED)) { | ||
completionNotifier(newValue) | ||
} | ||
} | ||
|
||
val localFileUrl: URL = LeafletMapView::class.java.getResource("/leafletmap/leafletmap.html") | ||
webEngine.load(localFileUrl.toExternalForm()) | ||
} | ||
|
||
private fun executeMapSetupScripts(mapConfig: MapConfig) { | ||
|
||
// execute scripts for layer definition | ||
mapConfig.layers.forEachIndexed { i, layer -> | ||
execScript("var layer${i + 1} = ${layer.javaScriptCode};") | ||
} | ||
|
||
val jsLayers = mapConfig.layers | ||
.mapIndexed { i, layer -> "'${layer.displayName}': layer${i + 1}" } | ||
.joinToString(", ") | ||
execScript("var baseMaps = { $jsLayers };") | ||
|
||
// execute script for map view creation (Leaflet attribution must not be a clickable link) | ||
execScript(""" | ||
|var myMap = L.map('map', { | ||
| center: new L.LatLng(${mapConfig.initialCenter.latitude}, ${mapConfig.initialCenter.longitude}), | ||
| zoom: 8, | ||
| zoomControl: false, | ||
| layers: [layer1] | ||
|}); | ||
| | ||
|var attribution = myMap.attributionControl; | ||
|attribution.setPrefix('Leaflet');""".trimMargin()) | ||
|
||
// execute script for layer control definition if there are multiple layers | ||
if (mapConfig.layers.size > 1) { | ||
execScript(""" | ||
|var overlayMaps = {}; | ||
|L.control.layers(baseMaps, overlayMaps).addTo(myMap);""".trimMargin()) | ||
|
||
} | ||
|
||
// execute script for scale control definition | ||
if (mapConfig.scaleControlConfig.show) { | ||
execScript("L.control.scale({position: '${mapConfig.scaleControlConfig.position.positionName}', " + | ||
"metric: ${mapConfig.scaleControlConfig.metric}, " + | ||
"imperial: ${!mapConfig.scaleControlConfig.metric}})" + | ||
".addTo(myMap);") | ||
} | ||
|
||
// execute script for zoom control definition | ||
if (mapConfig.zoomControlConfig.show) { | ||
execScript("L.control.zoom({position: '${mapConfig.zoomControlConfig.position.positionName}'})" + | ||
".addTo(myMap);") | ||
} | ||
} | ||
|
||
/** | ||
* Sets a marker at the specified position. | ||
* | ||
* @param position marker position | ||
* @param title marker title shown in tooltip (pass empty string when tooltip not needed) | ||
* @param marker marker color | ||
* @param zIndexOffset zIndexOffset (higher number means on top) | ||
* @return variable name of the created marker | ||
*/ | ||
fun addMarker(position: LatLong, title: String, marker: ColorMarker, zIndexOffset: Int): String { | ||
val varName = "marker${varNameSuffix++}" | ||
|
||
execScript("var $varName = L.marker([${position.latitude}, ${position.longitude}], " | ||
+ "{title: '$title', icon: ${marker.iconName}, zIndexOffset: $zIndexOffset}).addTo(myMap);") | ||
return varName; | ||
} | ||
|
||
/** | ||
* Moves the existing marker specified by the variable name to the new position. | ||
* | ||
* @param markerName variable name of the marker | ||
* @param position new marker position | ||
*/ | ||
fun moveMarker(markerName: String, position: LatLong) { | ||
execScript("$markerName.setLatLng([${position.latitude}, ${position.longitude}]);") | ||
} | ||
|
||
/** | ||
* Draws a track path anlong the specified positions in the color red and zooms the map to fit the track perfectly. | ||
* | ||
* @param positions list of track positions | ||
*/ | ||
fun addTrack(positions: List<LatLong>) { | ||
|
||
val jsPositions = positions | ||
.map { " [${it.latitude}, ${it.longitude}]" } | ||
.joinToString(", \n") | ||
|
||
execScript(""" | ||
|var latLngs = [ | ||
|$jsPositions | ||
|]; | ||
|var polyline = L.polyline(latLngs, {color: 'red', weight: 2}).addTo(myMap); | ||
|myMap.fitBounds(polyline.getBounds());""".trimMargin()) | ||
} | ||
|
||
private fun execScript(script: String) = webEngine.executeScript(script) | ||
} |
24 changes: 24 additions & 0 deletions
24
leafletmap/src/main/kotlin/de/saring/leafletmap/MapConfig.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package de.saring.leafletmap | ||
|
||
/** | ||
* Class for defining the layers and controls in the map to be shown. | ||
* @author Stefan Saring | ||
*/ | ||
class MapConfig @JvmOverloads constructor( | ||
|
||
/** | ||
* List of layers to be shown in the map, the default layer is OpenStreetMap. If more than one layer is | ||
* specified, then a layer selection control will be shown in the top right corner. | ||
*/ | ||
val layers: List<MapLayer> = listOf(MapLayer.OPENSTREETMAP), | ||
|
||
/** Zoom control definition, by default it's shown in the top left corner. */ | ||
val zoomControlConfig: ZoomControlConfig = ZoomControlConfig(), | ||
|
||
/** Scale control definition, by default it's not shown. */ | ||
val scaleControlConfig: ScaleControlConfig = ScaleControlConfig(), | ||
|
||
/** Initial center position of the map (default is London city). */ | ||
val initialCenter: LatLong = LatLong(51.505, -0.09) | ||
) |
40 changes: 40 additions & 0 deletions
40
leafletmap/src/main/kotlin/de/saring/leafletmap/MapLayer.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
package de.saring.leafletmap | ||
|
||
/** | ||
* Enumeration for all supported map layers. | ||
* | ||
* @author Stefan Saring | ||
*/ | ||
enum class MapLayer(val displayName: String, val javaScriptCode: String) { | ||
|
||
/** OpenStreetMap layer. */ | ||
OPENSTREETMAP("OpenStreetMap", """ | ||
L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { | ||
attribution: 'Map data © OpenStreetMap and contributors', | ||
})"""), | ||
|
||
/** OpenCycleMap layer. */ | ||
OPENCYCLEMAP("OpenCycleMap", """ | ||
L.tileLayer('http://{s}.tile.opencyclemap.org/cycle/{z}/{x}/{y}.png', { | ||
attribution: '© OpenCycleMap, Map data © OpenStreetMap contributors', | ||
})"""), | ||
|
||
/** Hike & bike maps layer (HikeBikeMap.org). */ | ||
HIKE_BIKE_MAP("Hike & Bike Map", """ | ||
L.tileLayer('http://{s}.tiles.wmflabs.org/hikebike/{z}/{x}/{y}.png', { | ||
attribution: '© HikeBikeMap.org, Map data © OpenStreetMap and contributors', | ||
})"""), | ||
|
||
/** MTB map (mtbmap.cz). */ | ||
MTB_MAP("MTB Map", """ | ||
L.tileLayer('http://tile.mtbmap.cz/mtbmap_tiles/{z}/{x}/{y}.png', { | ||
attribution: '© OpenStreetMap and USGS', | ||
})"""), | ||
|
||
/** MapBox layer in streets mode (consider: a project specific access token is required!). */ | ||
MAPBOX("MapBox", """ | ||
L.tileLayer('https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token=pk.eyJ1IjoibWFwYm94IiwiYSI6ImNpejY4NXVycTA2emYycXBndHRqcmZ3N3gifQ.rJcFIG214AriISLbB6B5aw', { | ||
id: 'mapbox.streets', | ||
attribution: 'Map data © OpenStreetMap contributors, Imagery © Mapbox' | ||
})""") | ||
} |
Oops, something went wrong.