MyDigitalLife

Project/package goedegep.mapview

Project Status: under development

About this project

This project is a JavaFx map view. It is based on the project gluon-oss-maps.
Gluon Maps:

  • Requires the MapView to be part of a Scene
  • Works asynchronously

I want to save map images to a file and then these properties aren’t very handy.
That was the reason for me to start this project, which also provides a MapImage for storing a map image to a file.

Functionality

FunctionalityMapViewMapImage

Overview

Current implementation

MapView.setZoom()
only calls baseMap.setZoom()
baseMap.setZoom()
calls prefZoom.set(z) this triggers baseMap.doZoom(prefZoom.get())
baseMap.doZoom()
calls zoom.set(z) this triggers MapTile.calculatePosition() on all the current tiles !!
MapTile.calculatePosition()
calculates and set scale x/y and translate x/y on the tile
calls baseMap.doSetCenter(lat, lon)
baseMap.doSetCenter(lat, lon)
sets lat, lon what in this case are the same values as the values with which it is called
sets translate x/y on the baseMap this triggers MapTile.calculatePosition() on all the current tiles 2 times !!
calls baseMap.markDirty()
baseMap.markDirty()
calls baseMap.calculateCenterCoords()
baseMap.calculateCenterCoords()
calls calculateCoords() with centerLat, centerLon
baseMap.calculateCoords()
sets (in this case) centerLat, centerLon this triggers MapView.markDirty() 2 times !!
MapView.markDirty()
calls setNeedsLayout(true) for this MapView
setNeedsLayout(true) for this BaseMap and request next pulse

BaseMap.layoutChildren()
calls baseMap.loadTiles()
baseMap.loadTiles()
loads needed tiles
calls cleanupTiles()
deletes tiles not needed anymore

MapView.layoutChildren()

New implementation

Use less listeners. In the current implementation ‘updating’ part happens too often.

Try to have a single zoom implementation. setZoom zooms around the center, zoom with mouse around mouse pointer

zoom in/out via API or mouse, panning, resizing
delete/add tiles
update tiles
update center
update size (only when resizing and happens automatic)
update layers

Dirty handling
A layer requests update if its contents changes, items to be shown are updated before requesting update
A layer needs an update if any of the map view paramenters change, items to be shown may change

If a layer requested an update, base map doesn’t have to be updated.

Dependencies
TileImageViewAbstract – no dependencies
mapview.TileImageView – no dependencies, provides progress
image.TileImageView – no dependencies
TileRetriever – interface
OsmTileRetriever – no dependencies
CachedOsmTileRetriever – no dependencies

Class MapViewAbstract

This is the main class of a map view, defining its interface and it is a (extends) javafx.scene.layout.Region, so you can directly add it to your GUI.
The map view provides the following functionality:

  • Base map
    The map view shows a base map, based on OSM map tiles.
  • Zooming
    The API provides methods to get and set the zoom level; getZoom() and setZoom().
    Besides this you can zoom in and out with the mouse wheel. In this case the location under the mouse pointer stays fixed.

Class MapViewCommon

This class implements the common part of the map view implementations.

Class goedegep.mapview.maptile.TileImageViewAbstract.

This abstract class forms the common part of a TileImageView. A TileImageView is a javafx.scene.image.ImageView, which retrieves its Image from a TileRetriever.

Class goedegep.mapview.view.impl.TileImageView

This TileImageView loads its map tile image asynchronously, which adds quite some complexity.
The tile is loaded using a TileRetriever.

Class goedegep.mapview.image.impl.TileImageView

This TileImageView loads its map tile image synchronously.
The tile is loaded using a TileRetriever.

Interface goedegep.mapview.maptile.TileRetriever

This interfaces defines the methods for retrieving map tiles.
There are two methods for retrieving tiles: one asynchronous and one synchronous.
Implementations may fetch the tiles directly from the tiles server, or they can cache the tiles on local storage.

CompletableFuture loadTile(int zoom, long i, long j)
This asynchronous method returns a CompletableFuture that will complete with the tile image once it is loaded.

Image loadTileSynchronously(int zoom, long i, long j)
This synchronous method blocks until the tile is loaded and returns the image directly.

Class goedegep.mapview.maptile.osm.OsmTileRetriever

This is an implementation of the TileRetriever interface that fetches the tiles directly from the OpenStreetMap (OSM) tile server.
The OpenStreetMap tile server provides map tiles in the format of PNG images, which can be accessed via a URL pattern. This class constructs the appropriate URL for the requested tile and loads the image synchronously or asynchronously.
In the requests to the OSM tile server a User-Agent is set. For this the application has to set the system property “http.agent”. If this isn’t set a warning will be logged and a default value will be used. This may lead to requests being denied from the server.

Class goedegep.mapview.maptile.osm.CachedOsmTileRetriever

This class is an implementation of the TileRetriever interface that caches tiles on the file system.
This class extends the OSMTileRetriever, which it uses to retrieve the tile data from the OpenStreetMap (OSM) tile server.

Calculations

The implementation is based on OSM map tiles. See Zoom levels to learn more about the OSM zoom levels.
At zoom level 0, there is one tile covering the whole earth. At each higher level, each tile is split in 2 by 2 tiles. So the number of tiles (n) in each direction at zoom level z is:

n = 2 ^ z

Furthermore for the map the Mercator projection is used.
Let the center location of the map be specified by its latitude (latdegrees) and longitude (londegrees), both in decimal degrees.
The tile index in the x direction is:

xtileindex = n / 360 * (180 + londegrees)
The longitude range is: -180 .. 180 degrees. So with this calculation, for londegrees equals -180 degrees, xtileindex is 0. For londegrees equals +180 degrees, xtileindex is n.
The Mercator formula is based on radians, so we convert the latitude from degrees to radians. 360 degrees equals 2 π radians, so π radians equals 180 degrees.

latradians = π * latdegrees / 180

The Mercator formula is:

y = R * ln(tan(π/4 + latradians/4)) or y = R * ln(sec(latradians) + tan(latradians))

As sec(latradians) = 1/cos(latradians) the formula can also be written as:

y = R * ln(tan(latradians) + 1 / cos(latradians))

Which is the format used in the implementation.

If you want to know more about map projections, read An Album Of Map Projections.

Functional

Covering tiles

If a tile to be shown isn’t available yet, and the tile of the next lower zoom level that covers this tile is available, then this tile is shown.
The method loadTiles() in the BaseMap installs all needed tiles. If a tile isn’t in its tile map, it creates a new MapTile. It assumes that this may take some time, so it calls getCoveringTile() to try to obtain the covering tile from the tile map. If there is a covering tile, the tile is added to the coveredTiles of the covering tile and the covering tile is added to the children of the BaseMap. The tile itself is always added as a child of the BaseMap.
The covering tile listens to the progress of the covered tiles. If a covered tile becomes available, it is removed from the coveredTiles. If