# Add custom layers The HERE SDK provides extensive options for customizing your map with the [HERE Style Editor](map-style-editor.md) and the [Style](custom-map-styles-style-guide.md) class. More options are described below. * **Custom raster layers:** Overlay tiled images on top of your map schemes with raster data sources. * **Point, tile, and polygon custom layers:** Use point, tile and polygon layers to add customizable content to the map. * **Tile data sources for point, tile, and polygon custom layers:** Efficiently manage and display large datasets by supplying point, tile, or polygon sources. ## Add custom raster layers With custom raster layers you can add your own raster tile service on top of the HERE `MapScheme` styles. This can be your own server where you host tiles that you want to show as an overlay on top of selected areas of the world - or a public tile server such as [OpenStreetMap](https://www.openstreetmap.org/#map=6/47.835/8.899). Fully opaque and transparent map tile overlays are supported. It is also possible to add more than one raster layer to the map at the same time. > #### Note > > Note that this is a beta release of this feature, so there could be a few bugs and unexpected behaviors. Related APIs may change for new releases without a deprecation process. To add custom raster layers, you need to create a `RasterDataSource`. A `RasterDataSource` represents the source of raster tile data to display. It also allows changing its configuration. With a `RasterDataSourceConfiguration` you can specify a configuration for the data source, including URL, tiling scheme, storage levels and caching parameters. Finally, with the `MapLayerBuilder` you can create a `MapLayer` to add a renderable map overlay to the map. * Use `MapLayerVisibilityRange` to specify at which zoom levels the map layer should become visible. * Use the `MapLayerPriority` to specify the draw order of the `MapLayer`. * Use the `MapContentType` to specify the type of data to be shown by the `MapLayer`. * Optionally, use the `Style` API to adjust properties such as opacity or brightness at runtime. More about this can be found in the [style guide for custom layers](custom-map-styles-style-guide.md). In case of raster tile images, use `MapContentType.RASTER_IMAGE`. Default map gesture actions such as pinch, rotate and zoom behave in the same way for raster tiles as for HERE vector maps - except for a few differences: for example, raster tiles are loaded as bitmaps and therefore a rotated raster map tile rotates all labels and street names contained together with the tile. > #### Note > > When loading a map scene with a custom map style or the default map style, the map will be rendered using vector tiles where the map information is represented as vector data consisting of vertices and paths for better scalability and performance. By contrast, raster tiles are regularly spaced and square, and consist of bitmap images that represent only pixel information. Note that the satellite map style is also raster based. Create a `RasterDataSource` as follows: ```java Java private RasterDataSource createRasterDataSource(String dataSourceName) { // Note: As an example, below is an URL template of an outdoor layer from thunderforest.com. // On their web page you can register a key. Without setting a valid API key, the tiles will // show a watermark. // More details on the terms of usage can be found here: https://www.thunderforest.com/terms/ // For example, your application must have working links to https://www.thunderforest.com // and https://www.osm.org/copyright. // For the below template URL, please pay attention to the following attribution: // Maps © www.thunderforest.com, Data © www.osm.org/copyright. // Alternatively, choose another tile provider or use the (customizable) map styles provided by HERE. String templateUrl = "https://tile.thunderforest.com/outdoors/{z}/{x}/{y}.png"; // The storage levels available for this data source. Supported range [0, 31]. List storageLevels = Arrays.asList(2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16); RasterDataSourceConfiguration.Provider rasterProviderConfig = new RasterDataSourceConfiguration.Provider( TileUrlProviderFactory.fromXyzUrlTemplate(templateUrl), TilingScheme.QUAD_TREE_MERCATOR, storageLevels); // Raster tiles are stored in a separate cache on the device. String path = "cache/raster/mycustomlayer"; long maxDiskSizeInBytes = 1024 * 1024 * 128; // 128 MB RasterDataSourceConfiguration.Cache cacheConfig = new RasterDataSourceConfiguration.Cache(path, maxDiskSizeInBytes); // Note that this will make the raster source already known to the passed map view. return RasterDataSource(mapView.getMapContext(), new RasterDataSourceConfiguration(dataSourceName, rasterProviderConfig, cacheConfig)); } ``` ```kotlin Kotlin private fun createRasterDataSource(dataSourceName: String): RasterDataSource { // Note: As an example, below is an URL template of an outdoor layer from thunderforest.com. // On their web page you can register a key. Without setting a valid API key, the tiles will // show a watermark. // More details on the terms of usage can be found here: https://www.thunderforest.com/terms/ // For example, your application must have working links to https://www.thunderforest.com // and https://www.osm.org/copyright. // For the below template URL, please pay attention to the following attribution: // Maps © www.thunderforest.com, Data © www.osm.org/copyright. // Alternatively, choose another tile provider or use the (customizable) map styles provided by HERE. val templateUrl = "https://tile.thunderforest.com/outdoors/{z}/{x}/{y}.png" // The storage levels available for this data source. Supported range [0, 31]. val storageLevels: List = mutableListOf(2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16) val rasterProviderConfig = RasterDataSourceConfiguration.Provider( TileUrlProviderFactory.fromXyzUrlTemplate(templateUrl)!!, TilingScheme.QUAD_TREE_MERCATOR, storageLevels ) // If you want to add transparent layers then set this to true. rasterProviderConfig.hasAlphaChannel = false // Raster tiles are stored in a separate cache on the device. val path = "cache/raster/mycustomlayer" val maxDiskSizeInBytes = (1024 * 1024 * 128).toLong() // 128 MB. val cacheConfig = RasterDataSourceConfiguration.Cache(path, maxDiskSizeInBytes) // Note that this will make the raster source already known to the passed map view. return RasterDataSource( mapView.mapContext, RasterDataSourceConfiguration(dataSourceName, rasterProviderConfig, cacheConfig) ) } ``` Note that - if desired - you can also hook into the calls of the `templateUrl` like so: ```java RasterDataSourceConfiguration.Provider rasterProviderConfig = new RasterDataSourceConfiguration.Provider( TilingScheme.QUAD_TREE_MERCATOR, storageLevels, new TileUrlProviderCallback() { @NonNull @Override public String onTileUrlRequest(int x, int y, int level) { return "https://tile.thunderforest.com/outdoors/" + level + "/" + x + "/" + y +".png"; } }); ``` The latter gives you an option to listen to each tile request and, for example, allows for finer control on the requested tiles, if needed. Note that custom raster layers use their own cache directory, which can be independent of the [map cache](tips-engines.md#work-with-the-map-cache) that is used for vector-based map data. Find more details in the API Reference for the `RasterDataSourceConfiguration`. This code uses a tile source from [Thunderforest](https://www.thunderforest.com/). More details on the terms of usage can be found on [https://www.thunderforest.com/terms/](https://www.thunderforest.com/terms/) and on [https://www.osm.org/copyright](https://www.osm.org/copyright). Note that you can also use other tile sources that follow the [OSM standard style](https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames) format `/zoom/x/y.png`. The `templateURL` should look like this: `https://YourRasterTileService.com/{zoom}/{xTile}/{yTile}.png` Here, the `zoom` value represents the map's current zoom level, and `xTile` and `yTile` defines the horizontal and vertical tile numbers. For example, to show the standard OSM map, use the following template URL: `let templateUrl = "https://a.tile.openstreetmap.org/{z}/{x}/{y}.png"` More tile servers following the OSM format are listed [here](https://wiki.openstreetmap.org/wiki/Tile_servers). Note that the HERE SDK supports only tile servers. Servers that provide vector data are not supported. Vector based tiles can only be used with the HERE Style Editor and the embedded map styles (see above). Once a tile source is created, a `MapLayer` can be built: ```java Java private MapLayer createMapLayer(String dataSourceName) { // The layer should be rendered on top of other layers including the "labels" layer // so that we don't overlap the raster layer over POI markers. MapLayerPriority priority = new MapLayerPriorityBuilder().renderedAfterLayer("labels").build(); // And it should be visible for all zoom levels. The minimum tilt level is 0 and maximum zoom level is 23. MapLayerVisibilityRange range = new MapLayerVisibilityRange(MapCameraLimits.MIN_TILT, MapCameraLimits.MAX_ZOOM_LEVEL); try { // Build and add the layer to the map. MapLayer mapLayer = new MapLayerBuilder() .forMap(mapView.getHereMap()) // mandatory parameter .withName(dataSourceName + "Layer") // mandatory parameter .withDataSource(dataSourceName, MapContentType.RASTER_IMAGE) .withPriority(priority) .withVisibilityRange(range) .build(); return mapLayer; } catch (MapLayerBuilder.InstantiationException e) { throw new RuntimeException(e.getMessage()); } } ``` ```kotlin Kotlin private fun createMapLayer(dataSourceName: String): MapLayer { // The layer should be rendered on top of other layers including the "labels" layer // so that we don't overlap the raster layer over POI markers. val priority = MapLayerPriorityBuilder().renderedAfterLayer("labels").build() // And it should be visible for all zoom levels. The minimum tilt level is 0 and maximum zoom level is 23. val range = MapLayerVisibilityRange(MapCameraLimits.MIN_TILT, MapCameraLimits.MAX_ZOOM_LEVEL) try { // Build and add the layer to the map. val mapLayer = MapLayerBuilder() .forMap(mapView.hereMap) // mandatory parameter .withName(dataSourceName + "Layer") // mandatory parameter .withDataSource(dataSourceName, MapContentType.RASTER_IMAGE) .withPriority(priority) .withVisibilityRange(range) .build() return mapLayer } catch (e: MapLayerBuilder.InstantiationException) { throw RuntimeException(e.message) } } ``` Above we reference the "labels" layer. More information on existing layers and their names can be found in the API Reference for the `MapLayerPriorityBuilder`. Finally, the visibility can be controlled by enabling or disabling the layer as shown below. Note that we also need to provide a unique name. Each `RasterDataSource` can be created only once: ```java String dataSourceName = "myRasterDataSourceOutdoorStyle"; rasterDataSourceOutdoorStyle = createRasterDataSource(dataSourceName); rasterMapLayerOutdoorStyle = createMapLayer(dataSourceName); rasterMapLayerOutdoorStyle.setEnabled(true); ``` The resulting layer looks like this: The above screenshot shows that you can easily combine custom raster tiles with other HERE SDK features. For example, you can render several `MapMarker` instances on top of the tile data from a tile server. > #### Note > > One of the main advantages of custom raster layers is that you can easily enhance the HERE map styles with a transparent custom tile source on top, for example, to show weather data or any other data you want to see on top of the map. When using an opaque raster tile source, it is recommended to combine this with an empty base map style. If you do not use an empty base map style, then the underlying map scheme will "shine" through until the raster layer tiles are loaded. The accompanying example app shows how this looks like. There are certain other parameters that you can adjust: * If your app uses multiple raster layers, you can define a load priority when building a layer with the `MapLayerBuilder`. This allows to specify an integer value: Higher values will lead to load the layer before layers with lower values. Note that this controls just the time when the layer is loaded. * The `MapLayerPriority` controls how the layer is rendered: For example, optionally, you can append `renderedLast()` which means that the layer will be rendered on top of all other layers. * For showing transparent map styles, set `rasterProviderConfig.hasAlphaChannel` to true. * For more parameters, please consult the API Reference. Another option to reduce the loading time while panning the map can be to adjust the [feature configuration](optimization.md) (only available for Navigate). ## Add custom layers to hold lines, polygons and points Another alternative to customize the map's appearance is by adding your own custom line, polygon and point layers with the `Style` class. This provides more flexibility to update the visual appearance at runtime. For most use cases, consider to use `MapMarker`, `MapPolyline` or `MapPolygon` instead. See the [map items](map-items.md) section. When you are working with a very large amount of custom data, consider to use [line, polygon and point tile sources](#add-custom-tile-sources) instead to ensure better performance and scalability. ### Add custom line layers Custom geodetic lines can be added to the map's appearance by adding your own custom data layer on top of the HERE `MapScheme` styles. A geodetic line is the shortest line between any two points on the Earth's surface, with each point represented through geodetic coordinates. > #### Note > > Note that this is a beta release of this feature, so there could be a few bugs and unexpected behaviors. Related APIs may change for new releases without a deprecation process. To add custom line layers, you need to create a `LineDataSource`. A `LineDataSource` represents the source of geodetic lines data to display. Each geodetic line can be coupled with a set of [custom data attributes](#add-custom-data-attributes-to-custom-layers) that can be used to customize the look of each line when displayed on top of the map. On top of a `LineDataSource`, with `MapLayerBuilder` you can create a `MapLayer` to add a renderable map overlay to the map. * Use `MapLayerVisibilityRange` to specify at which zoom levels the map layer should become visible. * Use the `MapLayerPriority` to specify the draw order of the `MapLayer`. * Use the `MapContentType` to specify the type of data to be shown by the `MapLayer`. In case of geodetic lines, use `MapContentType.LINE`. * Use the `Style` API to adjust properties such as width or color at runtime. More about this can be found in the [style guide for custom layers](custom-map-styles-style-guide.md). Create a `LineDataSource` as follows: ```java private final static String MY_CUSTOM_LINES_DATA_SOURCE_NAME = "MyCustomLines"; private LineDataSource createLineDataSource(String dataSourceName) { // Creates a new LineDataSource, without custom data. // To also add custom lines while creating the data source, `LineDataSourceBuilder.withPolyline` API can be used. return new LineDataSourceBuilder(mapView.getMapContext()).withName(dataSourceName).build(); } ``` Once a line data source is created, custom geodetic lines can be added to it: ```java private void addLinesToDataSource(LineDataSource linesDataSource) { // Prepare the geodetic list of points that make-up the line. ArrayList lineGeoPoints = new ArrayList(); lineGeoPoints.add(new GeoCoordinates(point1GeoLatitude, point1GeoLongitude)); ... lineGeoPoints.add(new GeoCoordinates(pointNGeoLatitude, pointNGeoLongitude)); // Optional: Prepare a list of custom attributes. DataAttributes lineAttributes = new DataAttributesBuilder().with("id", "my first line").build(); // Add the line to data source. linesDataSource.add(new LineDataBuilder().withGeometry(new GeoPolyline(lineGeoPoints)) .withAttributes(lineAttributes) .build()); // Repeat above steps to add more custom lines to the data source. // Alternatively, use `LineDataSource.add` to add multiple custom lines with a single call. ... } ``` Prepare a custom style for your lines layer: > #### Note > > The `layer` value in the style string must match the name given to the MapLayer in the next steps. In this example, the `dataSourceName` used is `MyCustomLines` and the layer name `MyCustomLinesLayer`. ```java private final static String MY_CUSTOM_LAYER_STYLE = "{\n" + " \"styles\": [\n" + " {\n" + " \"layer\": \"MyCustomLinesLayer\",\n" + " \"technique\": \"line\",\n" + " \"attr\": {\n" + " \"width\": 5.0\n", + " \"color\": \"#ff0000ff\"\n" + " }\n" + " }\n" + " ]\n" + "}"; private Style createCustomStyle() { try { return JsonStyleFactory.createFromString(MY_CUSTOM_LAYER_STYLE); } catch (JsonStyleFactory.InstantiationException) { // Custom exception handling. ... } return null; } ``` To display a `LineDataSource` on top of a map, a `MapLayer` can be built: ```java private MapLayer createMapLayer(String dataSourceName, Style layerStyle) { // Set the layer to be rendered on top of other layers. MapLayerPriority priority = new MapLayerPriorityBuilder().renderedLast().build(); // And it should be visible for all zoom levels. The minimum tilt level is 0 and maximum zoom level is 23. MapLayerVisibilityRange range = new MapLayerVisibilityRange(MapCameraLimits.MIN_TILT, MapCameraLimits.MAX_ZOOM_LEVEL); try { // Build and add the layer to the map. MapLayer mapLayer = new MapLayerBuilder() .forMap(mapView.getHereMap()) // mandatory parameter .withName(dataSourceName + "Layer") // mandatory parameter .withDataSource(dataSourceName, MapContentType.LINE) .withPriority(priority) .withVisibilityRange(range) .withStyle(layerStyle) .build(); return mapLayer; } catch (MapLayerBuilder.InstantiationException e) { throw new RuntimeException(e.getMessage()); } } ``` ### Add custom polygon layers Custom geodetic polygons can be added to the map's appearance by adding your own custom data layer on top of the HERE `MapScheme` styles. A geodetic polygon is defined by an exterior geodetic boundary, and optionally one or more interior boundaries. Each boundary is made from a closed sequence of geodetic points (the first point is the same as the last one). > #### Note > > Note that this is a beta release of this feature, so there could be a few bugs and unexpected behaviors. Related APIs may change for new releases without a deprecation process. To add custom polygon layers, you need to create a `PolygonDataSource`. A `PolygonDataSource` represents the source of geodetic polygon data to display. Each geodetic polygon can be coupled with a set of [custom data attributes](#add-custom-data-attributes-to-custom-layers) that can be used to customize the look of each polygon when displayed on top of the map. On top of a `PolygonDataSource`, with `MapLayerBuilder` you can create a `MapLayer` to add a renderable map overlay to the map. * Use `MapLayerVisibilityRange` to specify at which zoom levels the map layer should become visible. * Use the `MapLayerPriority` to specify the draw order of the `MapLayer`. * Use the `MapContentType` to specify the type of data to be shown by the `MapLayer`. In case of geodetic polygons, use `MapContentType.POLYGON`. * Use the `Style` API to adjust properties such as color at runtime. More about this can be found in the [style guide for custom layers](custom-map-styles-style-guide.md). Create a `PolygonDataSource` as follows: ```java private final static String MY_CUSTOM_POLYGONS_DATA_SOURCE_NAME = "MyCustomPolygons"; private PolygonDataSource createPolygonDataSource(String dataSourceName) { // Creates a new PolygonDataSource, without custom data. // To also add custom polygons while creating the data source, `PolygonDataSourceBuilder.withPolygon` API can be used. return new PolygonDataSourceBuilder(mapView.getMapContext()).withName(dataSourceName).build(); } ``` Once a polygon data source is created, custom geodetic polygons can be added to it: ```java private void addPolygonsToDataSource(PolygonDataSource polygonsDataSource) { // Prepare the geodetic list of points that make-up the polygon. // The point list must be closed and given in clockwise order. ArrayList polygonGeoPoints = new ArrayList(); polygonGeoPoints.add(new GeoCoordinates(point1GeoLatitude, point1GeoLongitude)); ... polygonGeoPoints.add(new GeoCoordinates(pointNGeoLatitude, pointNGeoLongitude)); ... polygonGeoPoints.add(new GeoCoordinates(point1GeoLatitude, point1GeoLongitude)); // Optional: Prepare a list of custom attributes. DataAttributes polygonAttributes = new DataAttributesBuilder().with("id", "my first polygon").build(); // Add the polygon to data source. polygonsDataSource.add(new PolygonDataBuilder().withGeometry(new GeoPolygon(polygonGeoPoints)) .withAttributes(polygonAttributes) .build()); // Repeat above steps to add more custom polygons to the data source. // Alternatively, use `PolygonDataSource.add` to add multiple custom polygons with a single call. ... } ``` Prepare a custom style for your polygons layer: > #### Note > > The `layer` value in the style string must match the name given to the MapLayer in the next steps. In this example, the `dataSourceName` used is `MyCustomPolygons` and the layer name `MyCustomPolygonsLayer`. ```java private final static String MY_CUSTOM_LAYER_STYLE = "{\n" + " \"styles\": [\n" + " {\n" + " \"layer\": \"MyCustomPolygonsLayer\",\n" + " \"technique\": \"polygon\",\n" + " \"attr\": {\n" + " \"color\": \"#ff0000ff\"\n" + " }\n" + " }\n" + " ]\n" + "}"; private Style createCustomStyle() { try { return JsonStyleFactory.createFromString(MY_CUSTOM_LAYER_STYLE); } catch (JsonStyleFactory.InstantiationException) { // Custom exception handling. ... } return null; } ``` To display a `PolygonDataSource` on top of a map, a `MapLayer` can be built: ```java private MapLayer createMapLayer(String dataSourceName, Style layerStyle) { // Set the layer to be rendered on top of other layers. MapLayerPriority priority = new MapLayerPriorityBuilder().renderedLast().build(); // And it should be visible for all zoom levels. The minimum tilt level is 0 and maximum zoom level is 23. MapLayerVisibilityRange range = new MapLayerVisibilityRange(MapCameraLimits.MIN_TILT, MapCameraLimits.MAX_ZOOM_LEVEL); try { // Build and add the layer to the map. MapLayer mapLayer = new MapLayerBuilder() .forMap(mapView.getHereMap()) // mandatory parameter .withName(dataSourceName + "Layer") // mandatory parameter .withDataSource(dataSourceName, MapContentType.POLYGON) .withPriority(priority) .withVisibilityRange(range) .withStyle(layerStyle) .build(); return mapLayer; } catch (MapLayerBuilder.InstantiationException e) { throw new RuntimeException(e.getMessage()); } } ``` ### Add custom point layers Custom geodetic points can be added to the map's appearance by adding your own custom data layer on top of the HERE `MapScheme` styles. > #### Note > > Note that this is a beta release of this feature, so there could be a few bugs and unexpected behaviors. Related APIs may change for new releases without a deprecation process. To add custom point layers, you need to create a `PointDataSource`. A `PointDataSource` represents the source of geodetic point data to display. Each geodetic point can be coupled with a set of [custom data attributes](#add-custom-data-attributes-to-custom-layers) that can be used to customize the look of each point when displayed on top of the map. On top of a `PointDataSource`, with `MapLayerBuilder` you can create a `MapLayer` to add a renderable map overlay to the map. * Use `MapLayerVisibilityRange` to specify at which zoom levels the map layer should become visible. * Use the `MapLayerPriority` to specify the draw order of the `MapLayer`. * Use the `MapContentType` to specify the type of data to be shown by the `MapLayer`. In case of geodetic points, use `MapContentType.POINT`. * Use the `Style` API to adjust properties such as icon at runtime. More about this can be found in the [style guide for custom layers](custom-map-styles-style-guide.md). Create a `PointDataSource` as follows: ```java private final static String MY_CUSTOM_POINTS_DATA_SOURCE_NAME = "MyCustomPoints"; private PointDataSource createPointDataSource(String dataSourceName) { // Creates a new PointDataSource, without custom data. // To also add custom points while creating the data source, `PointDataSourceBuilder.withPoint` API can be used. return new PointDataSourceBuilder(mapView.getMapContext()).withName(dataSourceName).build(); } ``` Once a point data source is created, custom geodetic points can be added to it: ```java private void addPointsToDataSource(PointDataSource pointDataSource) { // Prepare the geodetic point. GeoCoordinates geoPoint = new GeoCoordinates(pointGeoLatitude, pointGeoLongitude); // Optional: Prepare a list of custom attributes. DataAttributes pointAttributes = new DataAttributesBuilder().with("id", "my first point").build(); // Add the point to data source. pointDataSource.add(new PointDataBuilder().withCoordinates(geoPoint) .withAttributes(pointAttributes) .build()); // Repeat above steps to add more custom points to the data source. // Alternatively, use `PointDataSource.add` to add multiple custom points with a single call. ... } ``` Prepare a custom style for your points layer: > #### Note > > The `layer` value in the style string must match the name given to the MapLayer in the next steps. In this example, the `dataSourceName` used is `MyCustomPoints` and the layer name `MyCustomPointsLayer`. ```java private final static String MY_CUSTOM_LAYER_STYLE = "{\n" + " \"styles\": [\n" + " {\n" + " \"layer\": \"MyCustomPointsLayer\",\n" + " \"technique\": \"icon-text\",\n" + " \"attr\": {\n" + " \"icon-source\": \"my_icon.png\"\n" + " }\n" + " }\n" + " ]\n" + "}"; private Style createCustomStyle() { try { return JsonStyleFactory.createFromString(MY_CUSTOM_LAYER_STYLE); } catch (JsonStyleFactory.InstantiationException) { // Custom exception handling. ... } return null; } ``` To display a `PointDataSource` on top of a map, a `MapLayer` can be built: ```java private MapLayer createMapLayer(String dataSourceName, Style layerStyle) { // Set the layer to be rendered on top of other layers. MapLayerPriority priority = new MapLayerPriorityBuilder().renderedLast().build(); // And it should be visible for all zoom levels. The minimum tilt level is 0 and maximum zoom level is 23. MapLayerVisibilityRange range = new MapLayerVisibilityRange(MapCameraLimits.MIN_TILT, MapCameraLimits.MAX_ZOOM_LEVEL); try { // Build and add the layer to the map. MapLayer mapLayer = new MapLayerBuilder() .forMap(mapView.getHereMap()) // mandatory parameter .withName(dataSourceName + "Layer") // mandatory parameter .withDataSource(dataSourceName, MapContentType.POINT) .withPriority(priority) .withVisibilityRange(range) .withStyle(layerStyle) .build(); return mapLayer; } catch (MapLayerBuilder.InstantiationException e) { throw new RuntimeException(e.getMessage()); } } ``` ## Add custom data attributes to custom layers Any custom object such as a geodetic line can be coupled with a set of attributes. Such attributes are generic key-value pairs and can be freely defined by the developer. For example, such user-defined attributes can be used to tag each object with a unique identifier: ```java DataAttributes lineAttributes = new DataAttributesBuilder().with("id", "my first line").build(); ``` Or they can be used along with [style expressions](custom-map-styles-style-expressions.md) to customize the look of each object when displayed on top of the map: ```java // Define a layer style string that would use per-line object width and color values // that are stored inside line custom data attributes. For that purpose, the 'get' expression is used. private final static String MY_CUSTOM_LAYER_STYLE = "{\n" + " \"styles\": [\n" + " {\n" + " \"layer\": \"MyCustomLinesLayer\",\n" + " \"technique\": \"line\",\n" + " \"attr\": {\n" + " \"width\": [\"get\", \"lineWidthInMeters\"]\n", + " \"color\": [\"to-color\",[\"get\", \"lineColor\"]]\n" + " }\n" + " }\n" + " ]\n" + "}"; // Define specific values for "lineWidth" and "lineColor" per line object. DataAttributes lineAttributes = new DataAttributesBuilder().with("lineWidthInMeters", 10.0) .with("lineColor", "#ff0000ff").build(); ... ``` ## Add custom tile sources Tile source support for points, lines and polygons is an advanced feature designed for scenarios, where users need to integrate and visualize large volumes of custom geospatial data. For example, to visualize EV charging stations or shop locations around the globe. While APIs like `MapMarker` or `MapPolyline` are sufficient for smaller datasets, they become inefficient for large-scale use due to high memory consumption. Also, custom point, tile and polygon layers using custom data sources like `PointDataSource` are not suitable for large volumes of data. Instead, consider to use `PointTileDataSource`, `LineTileDataSource` or `PolygonTileDataSource` instead - as described in this chapter. > #### Note > > Note that the data is loaded on a per-tile basis, ensuring that the entire dataset is not loaded globally at once. ### Add custom point tile source You can enhance the map's appearance by adding a custom point tile source, allowing additional layers of point data to be rendered on top of the default HERE `MapScheme`. This approach enables seamless integration of point data, even when its format is not natively supported by the HERE SDK. A Point Tile Data Source can be used to display locations such as points of interest (POIs), real-time vehicle tracking, event markers, etc. > #### Note > > Note that this is a beta release of this feature, so there could be a few bugs and unexpected behaviors. Related APIs may change for new releases without a deprecation process. To add a custom point tile source, you need to create a `PointTileDataSource`. The `PointTileDataSource` facilitates the creation of point tile sources, while `PointTileSource` provides a mechanism to supply custom point data based on the requested `TileKey`. It uses JSON-based styling to control the appearance of the custom point tile source and integrates it into the map. On top of a `PointTileDataSource`, with `MapLayerBuilder` you can create a `MapLayer` to add a renderable map overlay to the map. * Use `MapLayerVisibilityRange` to specify at which zoom levels the map layer should become visible. * Use the `MapContentType` to specify the type of data to be shown by the `MapLayer`. In case of geodetic points, use `MapContentType.POINT`. * Use the `Style` API to adjust properties such as icon at runtime. Create a `PointTileDataSource` as follows: ```java private MapView mapView; private MapLayer pointMapLayer; private PointTileDataSource pointDataSource; public void onMapSceneLoaded(MapView mapView, Context context) { this.mapView = mapView; this.context = context; MapCamera camera = mapView.getCamera(); MapMeasure mapMeasureZoom = new MapMeasure(MapMeasure.Kind.DISTANCE_IN_METERS, DEFAULT_DISTANCE_TO_EARTH_IN_METERS); camera.lookAt(new GeoCoordinates(52.530932, 13.384915), mapMeasureZoom); String dataSourceName = "MyPointTileDataSource"; pointDataSource = createPointDataSource(dataSourceName); pointMapLayer = createMapLayer(dataSourceName); } ``` Create a custom point data source and load the tile: ```java private PointTileDataSource createPointDataSource(String dataSourceName) { // Create a PointTileDataSource using a local point tile source. // Note that this will make the point source already known to the passed map view. return PointTileDataSource.create(mapView.getMapContext(), dataSourceName, new LocalPointTileSource()); } @Override public LoadTileRequestHandle loadTile(@NonNull TileKey tileKey, @NonNull LoadResultHandler loadResultHandler) { // For each tile, provide the tile geodetic center as a custom point, with a single // named attribute "pointText" containing the tile key representation as a string. DataAttributes pointAttributes = new DataAttributesBuilder() .with("pointText", String.format("Tile: (%d, %d, %d)", tileKey.x, tileKey.y, tileKey.level)).build(); PointData tileData = new PointDataBuilder().withCoordinates(getTileCenter(tileKey)) .withAttributes(pointAttributes) .build(); loadResultHandler.loaded(tileKey, Arrays.asList(tileData), new TileMetadata(mDataVersion, new Date(0))); // No request handle is returned here since there is no asynchronous loading happening. return null; } ``` Prepare a custom style for your point tile source: > #### Note > > The `layer` value in the style string must match the name given to the MapLayer in the next steps. In this example, the `dataSourceName` used is `MyPointTileDataSource` and the layer name `MyPointTileDataSourceLayer`. ```java private final static String LAYER_STYLE = "{\n" + " \"styles\": [\n" + " {\n" + " \"layer\": \"MyPointTileDataSourceLayer\",\n" + " \"technique\": \"icon-text\",\n" + " \"attr\": {\n" + " \"text-color\": \"#ff0000ff\",\n" + " \"text-size\": 40,\n" + " \"text\": [\"get\", \"pointText\"]\n" + " }\n" + " }\n" + " ]\n" + "}"; private Style createCustomStyle() { try { return JsonStyleFactory.createFromString(LAYER_STYLE); } catch (JsonStyleFactory.InstantiationException e) { throw new RuntimeException(e); } } ``` To display a `pointTileSource` on top of a map, a `MapLayer` can be built: ```java // Creates a MapLayer for displaying custom point tiles. private MapLayer createMapLayer(String dataSourceName) { // The layer should be visible for all zoom levels. The minimum tilt level is 0 and maximum zoom level is 23. MapLayerVisibilityRange range = new MapLayerVisibilityRange(MapCameraLimits.MIN_TILT, MapCameraLimits.MAX_ZOOM_LEVEL); try { // Build and add the layer to the map. MapLayer mapLayer = new MapLayerBuilder() .forMap(mapView.getHereMap()) // mandatory parameter .withName(dataSourceName + "Layer") // mandatory parameter .withDataSource(dataSourceName, MapContentType.POINT) .withVisibilityRange(range) .withStyle(createCustomStyle()) .build(); return mapLayer; } catch (MapLayerBuilder.InstantiationException e) { throw new RuntimeException(e.getMessage()); } } ``` The resulting point tile source looks like this: ### Add custom line tile source You can enhance the map's appearance by adding a custom line tile source, allowing additional layers of line data to be rendered on top of the default HERE `MapScheme`. This approach enables seamless integration of line data, even when its format is not natively supported by the HERE SDK. A Line Tile Data Source can be ideal for rendering off-road tracks, boundaries, etc. > #### Note > > Note that this is a beta release of this feature, so there could be a few bugs and unexpected behaviors. Related APIs may change for new releases without a deprecation process. To add a custom line tile source, you need to create a `LineTileDataSource`. The `LineTileDataSource` facilitates the creation of line tile sources, while `LineTileSource` provides a mechanism to supply custom line data based on the requested `TileKey`. It uses JSON-based styling to control the appearance of the custom line tile source and integrates it into the map. On top of a `LineTileDataSource`, with `MapLayerBuilder` you can create a `MapLayer` to add a renderable map overlay to the map. * Use `MapLayerVisibilityRange` to specify at which zoom levels the map layer should become visible. * Use the `MapContentType` to specify the type of data to be shown by the `MapLayer`. In case of geodetic lines, use `MapContentType.LINE`. * Use the `Style` API to adjust properties such as icon at runtime. Create a `LineTileDataSource` as follows: ```java private MapView mapView; private MapLayer lineMapLayer; private LineTileDataSource lineDataSource; public void onMapSceneLoaded(MapView mapView, Context context) { this.mapView = mapView; this.context = context; MapCamera camera = mapView.getCamera(); MapMeasure mapMeasureZoom = new MapMeasure(MapMeasure.Kind.ZOOM_LEVEL, 9); camera.lookAt(new GeoCoordinates(52.530932, 13.384915), mapMeasureZoom); String dataSourceName = "MyLineTileDataSource"; lineDataSource = createLineDataSource(dataSourceName); lineMapLayer = createMapLayer(dataSourceName); if (lineMapLayer != null) { lineMapLayer.setEnabled(false); lineMapLayer.setStyle(createCustomStyle()); } } ``` Create a custom line data source and load the tile: ```java private LineTileDataSource createLineDataSource(String dataSourceName) { Log.d(TAG, "Creating line data source: " + dataSourceName); return LineTileDataSource.create(mapView.getMapContext(), dataSourceName, new LocalLineTileSource()); } @Override public LoadTileRequestHandle loadTile(@NonNull TileKey tileKey, @NonNull LoadResultHandler loadResultHandler) { Log.d("LocalLineTileSource", "Loading tile for key: " + tileKey.toString()); List lineCoordinates = getTileLineCoordinates(tileKey); if (lineCoordinates.isEmpty()) { Log.e("LocalLineTileSource", "No line coordinates generated for tile: " + tileKey); loadResultHandler.failed(tileKey); return null; } try { LineData tileData = new LineDataBuilder() .withGeometry(new GeoPolyline(lineCoordinates)) .withAttributes(new DataAttributesBuilder().build()) .build(); Log.d("LocalLineTileSource", "Tile loaded successfully"); loadResultHandler.loaded(tileKey, Collections.singletonList(tileData), new TileSource.TileMetadata(mDataVersion, new Date(0))); } catch (InstantiationErrorException e) { Log.e("LocalLineTileSource", "Failed to create LineData", e); loadResultHandler.failed(tileKey); } return null; } ``` Prepare a custom style for your line tile source: > #### Note > > The `layer` value in the style string must match the name given to the MapLayer in the next steps. In this example, the `dataSourceName` used is `MyLineTileDataSource` and the layer name `MyLineTileDataSourceLayer`. ```java private static final String LAYER_STYLE = "{ \n" + " \"styles\": [ \n" + " { \n" + " \"layer\": \"MyLineTileDataSourceLayer\", \n" + " \"technique\": \"line\", \n" + " \"attr\": { \n" + " \"color\": \"#FF0000\", \n" + " \"width\": [\"world-scale\", 5]\n" + " } \n" + " } \n" + " ] \n" + "}\n"; private Style createCustomStyle() { try { return JsonStyleFactory.createFromString(LAYER_STYLE); } catch (JsonStyleFactory.InstantiationException e) { Log.e(TAG, "Failed to create style: " + e.getMessage()); throw new RuntimeException(e); } } ``` To display a `lineTileSource` on top of a map, a `MapLayer` can be built: ```java private MapLayer createMapLayer(String dataSourceName) { // The layer should be visible for all zoom levels. The minimum tilt level is 0 and maximum zoom level is 23. MapLayerVisibilityRange range = new MapLayerVisibilityRange(MapCameraLimits.MIN_TILT, MapCameraLimits.MAX_ZOOM_LEVEL); try { return new MapLayerBuilder() .forMap(mapView.getHereMap()) .withName(dataSourceName + "Layer") .withDataSource(dataSourceName, MapContentType.LINE) .withVisibilityRange(range) .withStyle(createCustomStyle()) .build(); } catch (MapLayerBuilder.InstantiationException e) { Log.e(TAG, "Failed to create map layer: " + e.getMessage()); return null; } } ``` The resulting point tile source looks like this: ### Add custom raster tile source You can enhance the map's appearance by adding a custom raster tile source, allowing additional layers of raster data to be rendered on top of the default HERE `MapScheme`. This approach enables seamless integration of raster data, even when its format is not natively supported by the HERE SDK. A Raster Tile Source can overlay image-based data such as satellite imagery, weather maps, etc. > #### Note > > Note that this is a beta release of this feature, so there could be a few bugs and unexpected behaviors. Related APIs may change for new releases without a deprecation process. To add a custom raster tile source, you need to create a `RasterDataSource`. The `RasterDataSource` facilitates the creation of raster data sources, while `RasterTileSource` provides a mechanism to supply custom raster data based on the requested `TileKey`. It uses JSON-based styling to control the appearance of the custom raster tile source and integrates it into the map. On top of a `RasterDataSource`, with `MapLayerBuilder` you can create a `MapLayer` to add a renderable map overlay to the map. * Use `MapLayerVisibilityRange` to specify at which zoom levels the map layer should become visible. * Use the `MapContentType` to specify the type of data to be shown by the `MapLayer`. In case of geodetic rasters, use `MapContentType.RASTER_IMAGE`. * Use the `Style` API to adjust properties such as icon at runtime. Create a `RasterDataSource` as follows: ```java private MapView mapView; private MapLayer rasterMapLayerStyle; private RasterDataSource rasterDataSourceStyle; public void onMapSceneLoaded(MapView mapView, Context context) { this.mapView = mapView; this.context = context; MapCamera camera = mapView.getCamera(); MapMeasure mapMeasureZoom = new MapMeasure(MapMeasure.Kind.DISTANCE_IN_METERS, DEFAULT_DISTANCE_TO_EARTH_IN_METERS); camera.lookAt(new GeoCoordinates(52.530932, 13.384915), mapMeasureZoom); String dataSourceName = "myRasterDataSourceStyle"; rasterDataSourceStyle = createRasterDataSource(dataSourceName); rasterMapLayerStyle = createMapLayer(dataSourceName); // We want to start with the default map style. rasterMapLayerStyle.setEnabled(false); // Add a POI marker addPOIMapMarker(new GeoCoordinates(52.530932, 13.384915)); } ``` Create a custom raster data source: ```java private RasterDataSource createRasterDataSource(String dataSourceName) { // Create a RasterDataSource over a local raster tile source. // Note that this will make the raster source already known to the passed map view. return new RasterDataSource(mapView.getMapContext(), dataSourceName, new LocalRasterTileSource()); } @Override public LoadTileRequestHandle loadTile(@NonNull TileKey tileKey, @NonNull LoadResultHandler loadResultHandler) { // Pick one of the local tile images, based on the tile key x component. loadResultHandler.loaded(tileKey, mTileData.get(tileKey.x % mTileData.size()), new TileMetadata(mDataVersion, new Date(0))); // No request handle is returned here since there is no asynchronous loading happening. return null; } ``` To display a `rasterTileSource` on top of a map, a `MapLayer` can be built: ```java private MapLayer createMapLayer(String dataSourceName) { // The layer should be rendered on top of other layers except the "labels" layer // so that we don't overlap the raster layer over POI markers. MapLayerPriority priority = new MapLayerPriorityBuilder().renderedBeforeLayer("labels").build(); // And it should be visible for all zoom levels. The minimum tilt level is 0 and maximum zoom level is 23. MapLayerVisibilityRange range = new MapLayerVisibilityRange(MapCameraLimits.MIN_TILT, MapCameraLimits.MAX_ZOOM_LEVEL); try { // Build and add the layer to the map. MapLayer mapLayer = new MapLayerBuilder() .forMap(mapView.getHereMap()) // mandatory parameter .withName(dataSourceName + "Layer") // mandatory parameter .withDataSource(dataSourceName, MapContentType.RASTER_IMAGE) .withPriority(priority) .withVisibilityRange(range) .build(); return mapLayer; } catch (MapLayerBuilder.InstantiationException e) { throw new RuntimeException(e.getMessage()); } } ``` The resulting raster data source looks like this: > #### Note > > The creation of your custom road geometry or POIs requires HERE support and an additional agreement with HERE. Please contact your HERE representative for more details. See also [Use custom map catalogs](maps-byod.md). ## Try the example apps Most of the code snippets mentioned above are available in our "[CustomRasterLayers](https://github.com/heremaps/here-sdk-examples/tree/master/examples/latest/navigate/android/Java/CustomRasterLayers)" and "[CustomTileSource](https://github.com/heremaps/here-sdk-examples/tree/master/examples/latest/navigate/android/Java/CustomTileSource)" example apps. You can find these example apps on [GitHub](https://github.com/heremaps/here-sdk-examples) for your preferred platform. ## Related Topics * [Style guide for custom layers](https://docs.here.com/here-sdk/docs/custom-map-styles-style-guide) * [Style techniques reference for custom layers](https://docs.here.com/here-sdk/docs/custom-map-styles-style-techniques) * [Style expressions reference for custom layers](https://docs.here.com/here-sdk/docs/custom-map-styles-style-expressions)