Inspect HERE Platform Data with Leaflet

This chapter explains how you can use Leaflet JS to visualize GeoJSON or Protobuf-encoded data from a HERE platform catalog layer on top of the HERE-provided Optimized Map for Visualization (OMV) basemap or any other Leaflet-supported map provider of your choice.

The Data Inspector Library comes with two examples that demonstrate Leaflet usage for working with platform data:

  • The leaflet / geojson example demonstrates how you can use JavaScript to authenticate with the HERE platform and access GeoJSON data in the HERE GeoJSON Samples catalog with the HERE Data SDK for TypeScript, how to show the OMV basemap using the HARP plugin for Leaflet, and how to visualize the data on top of the basemap.

    GeoJSON data visualization with Leaflet
    Figure 1. GeoJSON data visualization with Leaflet
  • The leaflet / protobuf example demonstrates the same HERE platform authentication and basemap rendering mechanisms as the example above. The only difference is that leaflet / protobuf shows how to access Protobuf-encoded data in the Roads - Topology & Geometry layer from the HERE Map Content catalog, how to retrieve a schema from the layer and use it to decode the data into GeoJSON, and how to render the data atop the basemap.

    Encoded data visualization with Leaflet
    Figure 2. Encoded data visualization with Leaflet

Build a Sample Web App

To build a simple app that implements platform data visualization with Leaflet:

  1. Create an empty HTML file.
  2. Depending on what basemap you want to use and what type of platform data you want to visualize (GeoJSON or Protobuf-encoded data), add the necessary libraries to the <head> section of your HTML:

    • Leaflet library

      <script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.5.1/leaflet.js" charset="utf-8"></script>
      
    • Three.js library, harp.gl library, and harp.gl plugin for Leaflet for visualizing the HERE OMV basemap:

      <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/104/three.js" charset="utf-8"></script>
      <script src="https://unpkg.com/@here/harp.gl@0.10.0/dist/harp.js" charset="utf-8"></script>
      <script src="https://unpkg.com/@here/harp-leaflet@0.1.1/build/harp-leaflet.js" charset="utf-8"></script>
      
    • HERE Data SDK for TypeScript libraries:

      • olp-sdk-authentication for authenticating with the platform
      • olp-sdk-dataservice-read for reading platform data
      <script src="https://unpkg.com/@here/olp-sdk-authentication@1.2.1/bundle.umd.dev.js" charset="utf-8"></script>
      <script src="https://unpkg.com/@here/olp-sdk-dataservice-read@1.2.1/bundle.umd.dev.js" charset="utf-8"></script>
      
    • JSZip library for retrieving protobuf files from a platform layer schema and protobuf.js library for decoding Protobuf-encoded data to GeoJSON:

      <script src="https://unpkg.com/jszip@3.2.2/dist/jszip.js" charset="utf-8"></script>
      <script src="//cdn.rawgit.com/dcodeIO/protobuf.js/6.8.4/dist/protobuf.min.js"></script>
      
  3. Add your visualization script to the <head>:

    <script src="./your_script.js" charset="utf-8"></script>
    

    The chapters below explain in detail how you should implement the corresponding libraries to authenticate with the platform, retrieve layer data, and visualize the data on the basemap.

  4. Add an element where the Leaflet-supported map, including HERE OMV, will be placed in the <body> section:

    <div id="map" style="height: 100%"></div>
    

Authentication with the Platform

To authenticate with the platform, you have to use the function that returns an access token. In the UserAuth constructor, specify your app credentials - access key ID, secret, and project scope (optional):

// Get a function, that will provide a token to access HERE data.
const token = new UserAuth({
    env,
    credentials: {
        accessKeyId,
        accessKeySecret
    },
    tokenRequester: requestToken,
    scope
}).getToken();
const getToken = () => token;

For instructions on how to obtain these credentials, see Get Your Credentials.

Reading Platform Data

To fetch platform data, you need an instance of the layer with data:

/**
 * Fetch the layer config and return an instance of a layer client.
 *
 * @param hrn A catalog HRN.
 * @param layerId A layer ID.
 * @param settings Client settings.
 */
async function getLayerConfig(hrn, layerId, settings) {
    const catalogClient = new CatalogClient(HRN.fromString(hrn), settings);
    const config = await catalogClient.getCatalog(new CatalogRequest());
    return config.layers.find(item => item.id === layerId);
}

/**
 * Fetch the layer config and return an instance of a layer client.
 *
 * @param layerType A layer type.
 * @param hrn A catalog HRN.
 * @param layerId A layer ID.
 * @param settings Client settings.
 */
function getLayerClient(layerType, hrn, layerId, settings) {
    const hrnInstance = HRN.fromString(hrn);
    switch (layerType) {
        case "versioned":
            return new VersionedLayerClient(hrnInstance, layerId, settings);

        case "volatile":
            return new VolatileLayerClient(hrnInstance, layerId, settings);

        default:
            throw new Error(`Layer type "${layerType}" is not supported yet.`);
    }
}
// Get layer object, that will fetch GeoJSON data.
const settings = new OlpClientSettings({
    environment: env,
    getToken
});
const layerConfig = await getLayerConfig(hrn, layerId, settings);
if (!layerConfig) {
    throw new Error("Layer config was not found");
}

const layer = getLayerClient(layerConfig.layerType, hrn, layerId, settings);

If the layer data is Protobuf encoded, you need to provide a decoder to convert the encoded data to GeoJSON data. Usually, the decoder exists in a schema associated with the layer. That is why you have to load the schema and get the decoder from there:

/**
 * Fetch and process schema archive to prepare a Protobuf decoder.
 * 
 * @param layer Instance of a layer client.
 */
async function getDecoder(hrn, settings) {
    // Get schema with protobuf files
    const artifactClient = new ArtifactClient(settings);
    const detailsRequest = new SchemaDetailsRequest()
        .withSchema(HRN.fromString(hrn));
    const details = await artifactClient.getSchemaDetails(detailsRequest);

    if (details === undefined || details.variants === undefined) {
        return;
    }

    const variant = details.variants.find(item => item.id === "ds");
    if (variant === undefined) {
        return;
    }

    const request = new SchemaRequest().withVariant(variant);
    const archive = await artifactClient.getSchema(request);

    // Load schema as a ZIP archive
    const zip = new JSZip();
    await zip.loadAsync(archive);

    // Read all .proto file and parse them by Protobuf
    const protobufRoot = new protobuf.Root();
    Object.keys(zip.files).forEach(async fileName => {
        if (!fileName.endsWith(".proto")) {
            return;
        }

        const file = await zip.file(fileName).async("text");
        protobuf.parse(file, protobufRoot, { keepCase: true })
    });

    // Extract the manifest data.
    const manifestFile = await zip.file("META-INF/layer.manifest.json").async("text");
    const manifest = JSON.parse(manifestFile);

    return protobufRoot.lookupType(manifest.main.message);
}
if (layerConfig.schema === undefined || layerConfig.schema.hrn === undefined) {
    throw new Error("Layer schema HRN is not defined");
}

// Get a decoder from the schema.
const decoder = await getDecoder(layerConfig.schema.hrn, settings);
if (!decoder) {
    throw new Error("Decoder was not found.");
}

Request partitions to visualize and add them to Leaflet. If the data is in the GeoJSON format, then simply use the Leaflet's geoJSON method to visualize the data:

// Get tile data and add to Leaflet.
partitions.forEach(async partition => {
    const request = new DataRequest()
        .withPartitionId(`${partition}`);
    const response = await layer.getData(request);
    const data = await response.json();

    L.geoJSON(data)
        .bindPopup(item => item.feature.properties.tooltip)
        .addTo(map);
});

Encoded data must be first decoded, then the data can be converted into GeoJSON and visualized with the Leaflet's geoJSON method:

// Fetch, decode and visualize specific partitions.
partitions.forEach(async partition => {
    const request = new DataRequest()
        .withPartitionId(`${partition}`);
    const response = await layer.getData(request);
    const data = await response.arrayBuffer();

    const decodedData = decode(data, decoder);

    const geojson = decodedData.segment.map(segment => ({
        type: "Feature",
        properties: {
            tooltip: segment.identifier
        },
        geometry: {
            type: "LineString",
            coordinates: segment.geometry.point.map(point => [point.longitude, point.latitude])
        }
    }));

    L.geoJSON(geojson)
        .bindPopup(layer => layer.feature.properties.tooltip)
        .addTo(map);
});

Visualize HERE OMV Basemap

The code snippet below demonstrates how you can visualize the HERE OMV basemap with the harp.gl plugin for Leaflet:

// Add the HERE base map. This can be replaced with the code of your preferred base map.
const harpLeaflet = new L.HarpGL({
    theme: "https://unpkg.com/@here/harp-map-theme@0.17.0/resources/berlin_tilezen_night_reduced.json"
}).addTo(map);
const baseMapLayerConfig = await getLayerConfig(baseMapHRN, "omv-base-v2", settings);
if (!baseMapLayerConfig) {
    throw new Error("Base map layer was not found");
}
const baseMapLayer = getLayerClient(baseMapLayerConfig.layerType, baseMapHRN, "omv-base-v2", settings);
harpLeaflet.mapView.addDataSource(new harp.OmvDataSource({
    dataProvider: {
        async getTile(tileKey) {
            const request = new DataRequest()
                .withPartitionId(`${mortonCodeFromQuadKey(tileKey)}`);
            const response = await baseMapLayer.getData(request);
            return response.arrayBuffer();
        },
        async connect() {},
        async ready() { return true; }
    },
    layerName: "omv-base-v2",
    getBearerToken: getToken,
    styleSetName: "tilezen"
}));

results matching ""

    No results matching ""