# (4) - Leaflet JS meets Data Hub First, please login HERE Data Hub Console, create a new Token and grant "readFeatures" to the Spaces you just uploaded: dip slopes, faults and land liquefaction. ###### HERE Data Hub Console: https://xyz.api.here.com/token-ui/ ![](https://i.imgur.com/kcXO4UU.png) By the way, please make sure adding some notes in "DESCRIPTION", it can help remind you what the token is for, it's more convenient to manage. Press "Submit" to create new token. ![](https://i.imgur.com/UvQ9M5u.png) The new token and the IDs of these 3 Spaces will be used in the future, for exmple: * Token: AMveoMAsRiKUc32sreLzIgA * Land liquefaction: YZ9MdErx * Fault: 19msLC2t * Dip Slope: q1gtCeWZ Now we are ready to make a map. ## Leaflet JS ###### Leaflet JS website: https://leafletjs.com/ Leaflet JS is a open source map framework, written in JavaScript, it's very easy to use and support mobile devices. Although it's not the richest mapping library you can find, but the community behind it is very active, and there are many plugin developed to enhance the feature of LeafletJS. ![](https://i.imgur.com/Zhx7PMA.png) If you are interested, you can find tutorial, documents and plugin in official website. Now we will create a new empty map, you can use text editors you preferred, such as Notepad++, Visual Studio Code, Sublime, or even notepad. Create a new file, then save it as a .html file. First, please add CSS and JS libraries that is needed to work with LeafletJS. Also, a CSS style of "#map" is also defined, it sets the height of map is 100% which will occupy all space of your browser window. ```htmlmixed= <head> <meta http-equiv="content-type" content="text/html; charset=UTF-8" /> <!-- Load Leaflet from CDN --> <link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css" integrity="sha512-xodZBNTC5n17Xt2atTPuE1HxjVMSvLVW9ocqUKLsCC5CXdbqCmblAshOMAS6/keqq/sMZMZ19scR4PsZChSR7A==" crossorigin="" /> <!-- Make sure you put this AFTER Leaflet's CSS --> <script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js" integrity="sha512-XQoYMqMTK8LvdxXYG3nZ448hOEQiglfqkJs1NOQV44cWnUrBc8PkAOcXy20w0vlaXaVUearIOBhiXZ5V3ynxwA==" crossorigin=""></script> <style> #map { height: 100%; } </style> </head> ``` Declare a "div" with id "map" in body, then declare "script" below. ```htmlmixed= <body> <div id="map"></div> </body> <script> </script> ``` Declare a map object, with center of 23.773, 120.959 and zoom of 8, which will put Taiwan on the center of the map. ```javascript= var map = L.map( 'map', { center: [23.773, 120.959], zoom: 8, }); ``` Then add a scale bar to the bottom-left corner of map. ```javascript= L.control.scale({ position: 'bottomleft' }).addTo(map); ``` Then you will get a blank map like this. ![](https://i.imgur.com/xt2DX0x.png) The complete source codes as below: ```javascript= <head> <meta http-equiv="content-type" content="text/html; charset=UTF-8" /> <!-- Load Leaflet from CDN --> <link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css" integrity="sha512-xodZBNTC5n17Xt2atTPuE1HxjVMSvLVW9ocqUKLsCC5CXdbqCmblAshOMAS6/keqq/sMZMZ19scR4PsZChSR7A==" crossorigin="" /> <!-- Make sure you put this AFTER Leaflet's CSS --> <script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js" integrity="sha512-XQoYMqMTK8LvdxXYG3nZ448hOEQiglfqkJs1NOQV44cWnUrBc8PkAOcXy20w0vlaXaVUearIOBhiXZ5V3ynxwA==" crossorigin=""></script> <style> #map { height: 100%; } </style> </head> <body> <div id="map"></div> </body> <script> var map = L.map( 'map', { center: [23.773, 120.959], zoom: 8, }); L.control.scale({ position: 'bottomleft' }).addTo(map); </script> ``` But hold on...there is nothing in the map? What's going on? ## Know more about HERE mapping API Besides Data Hub API we've already introduced, HERE offers various mapping API and SDK, such as map display, satellite imagery, address search, places search, route calculation, or even more advanced logistic products such as truck routing, fleet telematics, vehicle dispatching, geofencing, waypoint sequecing, GPS trace analysis, advanced map layers and toll cost calculation. There services are delivered as easy-to-use web API for all the developers. HERE also offers Android and iOS mapping SDK. As of now, the mapping services delivered by HERE has become a large family, individual developer, small business, large enterprises and governments are all can be satisfied by it. ![](https://i.imgur.com/36BzvLf.png) ###### HERE developer website: https://developer.here.com Take a example of deliveries, we can HERE APIs to build our own mapping services. We can get latitudes and longitudes with addresses, then determine the best sequence by these latitudes and longitudes, finally calculate the detailed routes between these waypoints. After the sequence calculated, control center can send these destinations to the truck driver and use HERE navigation SDK to perform turn-by-turn navigation during delivery, getting warnings such as over speed driving. If we are planning to make a house safety map, we might need functions such as: 1. Background maps, we might need a normal map, satellite imagery and terrain map. 2. Display of map data, we've deployed them to Data Hub, now we are going to put them on the map. 3. Address search, input an address to get geographic location, then check if the location falls into risky areas. We will finish it step by step. ## HERE Freemium account You were not asked to input your credit card information while signing up a HERE Account, and the account your created is a "Freemium" account. Freemium means you won't be charged if your usage didn't exceed the quota, here is the detail: * 250000 monthly API transactions. * For most of APIs, one request equals one transaction, such as: * Search an address, is 1 transaction. * Calculate a route, is 1 transaction. * Some APIs has different rules. * 5000 monthly active user of HERE SDK. * 2.5 GB monthly data transmission of HERE Data Hub/Studio. * 5.0 GB monthly data storage of HERE Data Hub. * Service plan is not applied, you can seek support from community such as [stackoverflow.com](https://stackoverflow.com/questions/tagged/here-api). Because it's calculated monthly, so it will be reset on the 1st day of a month, you can control your usage to use it for free if no exceeding your monthly quote, however if it exceeds, your account will be locked temporary, and you will have to input credit card to reactivate your account, and pay for the usage beyond the limit. ![](https://i.imgur.com/MbRtmNq.png) ###### HERE pricing: https://developer.here.com/pricing If you are creating Pro account, the monthly fee is 499 Euro, you can use add-on features of Data Hub and get support from HERE team. For an individual or you are just do it for hobby, 250K transactions might be enough, it's averagely more than 8000 transactions a day. If you have more than 8000 transactions a day and more to come, maybe your service is very popular and you may want to get paid with it. I would say 250K monthly is fairly enough ## Generate your HERE API Key First please login your HERE account: https://developer.here.com/login 。 You will see a project named "Freemium 202x-xx-xx", press it. ![](https://i.imgur.com/EMarn7Y.png) Once entered, you will see a page of project information, monthly usage and JS API/REST API/SDK. You will find you have already have an APP ID of REST API, that's because you already login HERE Data Hub, it created an APP ID already for you. ![](https://i.imgur.com/Z1mR7mG.png) Now we will generate an API Key to proceed, API Key is an unique key for your to access HERE API, such as address search and route calculation. Press the button "Create API key" under REST, and it will show a hidden string, this is API Key, you can press the icon of eye to show or press "COPY" to copy to your clipboard. ![](https://i.imgur.com/Q9hHUvY.png) You can have 2 API Keys, removing or suspending them is also possible. ## Introducing HERE Map Tile API The function of HERE Map Tile API is to return a tiled image with X/Y/Z value that users input, it's the most essential element of web map service. ###### HERE Map Tile API: https://developer.here.com/documentation/map-tile/ HERE Map Tile API provides map of lots of styles, for example you can get a map tile with "Normal Day" style: `https://{SUB_DOMAIN}.{BASE_TYPE}.maps.ls.hereapi.com/maptile/2.1/{TILE_TYPE}/newest/{SCHEME}/{Z}/{X}/{Y}/{SIZE}/{FORMAT}?apiKey={API_KEY}` * SUB_DOMAIN * It's used for load balancing, you can send your requests randomly to 1-4 to shorten the loading time. * BASE_TYPE * aerial: satellite imagery * base: base map * traffic: map with traffic flow * TILE_TYPE * alabeltile: admin labels only * basetile: base map (map only, no labels) * blinetile: base map (no labels, no buildings) * labeltile: labels only * linetile: roads only * mapnopttile: map without public transit lines * maptile: normal map * streettile: street and labels * trucknopttile: special map scheme for trucks, without public transit * truckonlytile: special map scheme for trucks, only roads * trucktile: special map scheme for trucks * xbasetile: base map (no streets, no labels) * SCHEME(most used only) * normal.day * normal.day.grey * normal.day.transit * normal.night * normal.night.grey * reduced.day * terrain.day * satellite.day * hybrid.day * Z: Z level * X: X coordinate * Y: Y coordinate * SIZE * 256px * 512px * FORMAT * PNG * PNG8 * JPG * API_KEY * your API Key For example we can request a satellite image with Z = 10, X = 856, Y = 440 as we did in previous course of HERE Data Hub API: https://1.aerial.maps.ls.hereapi.com/maptile/2.1/maptile/newest/satellite.day/9/428/219/256/png8?apiKey={API_KEY} ![](https://i.imgur.com/e5wLzXD.png) Except calling a map, we can input parameter to request information we need, such as: * lg/lg2: language/secondary language * ara – Arabic * baq – Basque * cat – Catalan * chi – Chinese-simplified * cht – Chinese-traditional * cze – Czech * dan – Danish * dut – Dutch * eng – English * fin – Finnish * fre – French * ger – German * gle – Gaelic * gre – Greek * heb – Hebrew * hin – Hindi * ind – Indonesian * ita – Italian * mul – Multiple Languages * nor – Norwegian * per – Persian * pol – Polish * por – Portuguese * rus – Russian * sin – Sinhalese * spa – Spanish * swe – Swedish * tha – Thai * tur – Turkish * ukr – Ukrainian * urd – Urdu * vie – Vietnamese * wel – Welsh * ppi * 72: normal PC screen * 250: high resolution * 320: higher resolution * 500: super high resolution * pois * Display POI (Point of Interest) such as shops, parking lots. We can call a hybrid map to display POIs in Traditional Chinese, higher resolution in 512px size: https://3.aerial.maps.ls.hereapi.com/maptile/2.1/maptile/newest/hybrid.day/16/54889/28057/512/png8?lg=cht&ppi=320&pois&apiKey={API_KEY} ![](https://i.imgur.com/D6wcjg6.png) ## Add base map to Leaflet JS Normally we don't use map tiles manually, but using other way to stitch them to make a complete map. We can do this with LeafletJS by adding HERE Map tile to the map by adding **L.tileLayer** object to the map, so please add these codes to the map html file. ```javascript= var hereApiKey = 'YOUR API KEY'; var hereNormal = L.tileLayer('https://{s}.base.maps.ls.hereapi.com/maptile/2.1/maptile/newest/normal.day/{z}/{x}/{y}/256/png8?lg=cht&ppi=72&pois&apiKey=' + hereApiKey, { attribution: '© 2020 HERE', subdomains: [1, 2, 3, 4] }).addTo(map); ``` Save and reopen this file using browser, you will find Taiwan is at the center of map view. But we would need more map schemes, such as normal map, terrain map and hybrid map, if we add them all to the map, only the layer on the top can be shown, so we need a switch between them for us to choose which one to show. ![](https://i.imgur.com/azmuWYP.png) We've already added a layer of normal map, we will now add hybrid map and terrain map. ```javascript= var hereHybrid = L.tileLayer( 'https://{s}.aerial.maps.ls.hereapi.com/maptile/2.1/maptile/newest/hybrid.day/{z}/{x}/{y}/256/png8?lg=cht&ppi=72&pois&apiKey=' + hereApiKey, { attribution: '© 2020 HERE', subdomains: [1, 2, 3, 4] }); var hereTerrain = L.tileLayer( 'https://{s}.aerial.maps.ls.hereapi.com/maptile/2.1/maptile/newest/terrain.day/{z}/{x}/{y}/256/png8?lg=cht&ppi=72&pois&apiKey=' + hereApiKey, { attribution: '© 2020 HERE', subdomains: [1, 2, 3, 4] }); ``` You might notice there is no ".addTo(map)" at the end of 2 layers, that's because we don't need to add them to the map directly, but add them to the layer switch later on. Now we will declare 2 dictionaries: baseLayers and overlays. Add the 3 layers we have to baseLayers with their own names, and leave overlays blank. ```javascript= var baseLayers = { 'HERE Normal': hereNormal, 'HERE Hybrid': hereHybrid, 'HERE Terrain': hereTerrain }; var overlays = { }; ``` Then leave the widget of layer control (**L.control.layers**) to the map. ```javascript= L.control.layers(baseLayers, overlays, {collapsed: false}).addTo(map); ``` Upon completed, you can switch the base maps of the background with the switch on the top-right corner. ![](https://i.imgur.com/lHMYiYn.gif) ## Add Data Hub layer to LeafletJS After the base map is created, we are ready to add the content on the Data Hub, and what we are going to use here is the Data Hub API. You may also be curious here, why HERE itself provides JavaScript API, which is even more powerful than Leaflet JS, but why should I choose Leaflet JS instead of the JavaScript API provided by HERE? In fact, the main reason is that the UI components provided by Leaflet JS are more convenient and faster to make, and Leaflet JS's native support for GeoJSON objects also makes the code more concise. Of course, if you are interested in the functions provided by the HERE JavaScript API, you can also learn about it here: https://developer.here.com/documentation/maps/ Remember what I mentioned before, we are going to use the Data Hub API, there are two necessary parameters: "Token" and "Space ID". And to add the content of the Data Hub directly to the map without searching, there are three APIs that can be operated. 1. Use https://xyz.api.here.com/hub/spaces/{spaceId}/iterate to directly download all the contents of Space and add it to the map. * Advantages: It is the easiest to use as long as you call it once. * Disadvantages: If the amount of data is large, it will take a long time to download, which will affect the user experience. 2. Use https://xyz.api.here.com/hub/spaces/{spaceId}/bbox, but you must first define the east, south, west, and north boundaries, download the Space content within the boundary, and add it to the map . * Advantages: If the amount of data is extremely large, you can use this method to download a certain range of data (but it may still be very large). * Disadvantages: It may be called every time the map is moved, which affects the user experience. 3. Use https://xyz.api.here.com/hub/spaces/{spaceId}/tile/{type}/{tileId} to download the map tile by tile. * Advantages: Use image tiles to download a small amount of data in batches, and the user experience is relatively smooth. * Disadvantages: The operation method is more complicated. Therefore, we can use the amount of data to determine how to download the contents of the Data Hub. * The amount of data is small, you can use https://xyz.api.here.com/hub/spaces/{spaceId}/iterate. * For a large amount of data, https://xyz.api.here.com/hub/spaces/{spaceId}/tile/{type}/{tileId} can be used. Therefore, when we implement it, we will use the following methods: 1. The active fault distribution map, because there are only 66 features (characters), we use https://xyz.api.here.com/hub/spaces/{spaceId}/iterate. 2. The distribution map of soil liquefaction and downhill slope. There are a lot of features. We use https://xyz.api.here.com/hub/spaces/{spaceId}/tile/{type}/{tileId} . However, before this, because we need to download the data from the Data Hub API, there are two common ways, one is to write Ajax functions by ourselves, the other is to use jQuery, in order to make it easier for us to use jQuery here. ###### jQuery official website: https://jquery.com/ What is jQuery? jQuery is a set of JavaScript library that provides a series of convenient APIs. What we want to use here is his Ajax part. First add the following piece of code to the head, so that jQuery will be downloaded from the Internet as soon as the web page is opened. ```javascript= <script src="https://code.jquery.com/jquery-3.5.1.min.js" integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script> ``` Next, we first add three FeatureGroups to the map. The reason for doing this is because we not only want to put these features on the map, but also control their switches, and use FeatureGroup to put these maps. Only when the signs are placed in the same group can they be displayed as layers in L.control.layers. If you don't do this, it will become a switch to control each Feature, which is very inconvenient, and it is actually not feasible. So we will first create a FeatureGroup for the three layers that will be added later. ```javascript= var faultFeatureGroup = L.featureGroup(); //active fault layer var landLiquefactionFeatureGroup = L.featureGroup(); //Soil liquefaction layer var dipSlopeFeatureGroup = L.featureGroup(); //Downward slope layer ``` Then prepare the token that you have read permission. ```javascript= var dataHubReadToken ='Your Data Hub Token'; ``` Then we use jQuery's getJSON API to download the active tomogram from the Data Hub. ```javascript= var faultLayer = $.getJSON( 'https://xyz.api.here.com/hub/spaces/' + faultSpaceId +'/iterate?access_token=' + dataHubReadToken, value => { value.features.forEach(element => { L.geoJSON(element, { style: { color:'#0032ff', opacity: 0.8, weight: 8, fill: false } }).bindPopup(element.properties.Name).addTo(faultFeatureGroup); }); }); ``` In the above code, the value is the result returned by the Data Hub. The type of FeatureCollection will be directly converted into a JSON object. Then we have to read each Feature in FeatureColleciton, convert it into L.geoJSON object, set the style of each object, and bind a L.popup object to display the name (element.properties.Name). Finally, these L.geoJSON objects are added to faultFeatureGroup. Then we add this layer to L.control.layers. ```javascript= var overlays = { 'Active fault': faultFeatureGroup }; ``` If all goes well, the active fault will appear in the menu and you can switch it freely. ![](https://i.imgur.com/o342mCf.gif) And if you click on the fault line, the name will also appear. ![](https://i.imgur.com/A2ZTmPh.png) ## Read Data Hub tiles Using Leaflet KS, if you want to read the contents of the Data Hub in the form of tiles, you will encounter a problem: How do I know which tile to read? We can use two ways: 1. In a more traditional way, calculate the X and Y coordinates of all tiles on the screen by adding the Z level to the four sides of the map screen, and then read them one by one. 2. Use a plug-in. Because the native L.tileLayer of Leaflet JS only supports bitmaps (png, jpg), some powerful developers have developed a plug-in so that L.tileLayer can also read GeoJSON and arrange them on the map: https://github.com/ glenrobertson/leaflet-tilelayer-geojson/ The second way is left to your own research. We introduce the first method, because you can learn how to capture map events and better understand the concept of tiles, which will be very helpful for you to continue in-depth later. We can calculate the number of all tiles currently covered on the surface after each screen panning or zooming ends, using the following function: ```javascript= function getTileXYZ(bounds, zoom) { var min = map.project(bounds.getNorthWest(), zoom).divideBy(256).floor(), max = map.project(bounds.getSouthEast(), zoom).divideBy(256).floor(), coordsList = []; for (var i = min.x; i <= max.x; i++) { for (var j = min.y; j <= max.y; j++) { const coords = new L.Point(i, j); coords.z = zoom; console.log(coords); // print result at console } } } ``` Simply put, this function requires two parameters. The first is the L.latLngBounds object, which represents the four boundaries of the map screen, which can be obtained through L.map.getBounds(); the second is the zoom value, which represents The current Z level of the map can be obtained with L.map.getZoom(). The timing of the call has two: 1. After reading the map for the first time, we can use the "load" event. But to use the load event, we have to change the steps of map loading. 2. Every time the map screen is moved, we can use the "moveend" event. First put what we defined earlier: ```javascript= var map = L.map( 'map', { center: [23.773, 120.959], zoom: 8, }); ``` Change to: ```javascript= var tileList = []; // first create an array and store the list of tiles that will be generated after storage var map = L.map('map'); // Create an L.map object. map.on('load', function () { tileList = getTileXYZ(map.getBounds(), map.getZoom()); }); // Register a "load" event first, and wait to receive the first map reading map.setView([23.773, 120.959], 8); // Set the map position and Z level, and read the map ``` Then open the console, and you will see the output of a series of X/Y/Z coordinate information of the tiles. ![](https://i.imgur.com/RlcSlWo.png) Then register the "moveend" event again. ```javascript= map.on('moveend', function () { getTileXYZ(map.getBounds(), map.getZoom()); }); ``` In this way, when the map is read for the first time and every time it is moved, the coordinates of the tiles on the map will be calculated. ![](https://i.imgur.com/hNYyqp7.gif) Now that we can capture the events of the first reading and each movement, we can use these two events to read the content on the Data Hub. In the Data Hub API, we can use the API https://xyz.api.here.com/hub/spaces/{spaceId}/tile/{type}/{tileId, and the three most important parameters are: 1. spaceId: Your Space ID. 2. Type: Please fill in "`web`". 3. tileId: Please fill in "`z_x_y`", please replace z/x/y with the tile coordinate value variable calculated by the "getGeoJSONTiles" function. Let's do it below. Please change the "getTileXYZ" function just now to "getGeoJSONTiles" and modify the content: ```javascript= function getGeoJSONTiles(bounds, zoom, spaceId, accessToken, featureGroup) { featureGroup.clearLayers(); // 下載前先把 featureGroup 清空,以免重複的內容不斷疊加上去 var min = map.project(bounds.getNorthWest(), zoom).divideBy(256).floor(), max = map.project(bounds.getSouthEast(), zoom).divideBy(256).floor(); for (var i = min.x; i <= max.x; i++) { for (var j = min.y; j <= max.y; j++) { const coords = new L.Point(i, j); var x = coords.x, y = coords.y, z = zoom; $.getJSON('https://xyz.api.here.com/hub/spaces/' + spaceId + '/tile/web/' + z + '_' + x + '_' + y + '?access_token=' + accessToken, value => { value.features.forEach(element => { L.geoJSON(element).addTo(featureGroup); }); }) } } } ``` The original "getTileXYZ" function only accepts bounds and zoom, but in order to call the Data Hub API at the same time, we added three parameters to "getGeoJSONTiles", namely spaceId, accessToken and output featureGroup, so we use the same program The code can handle multiple Spaces. The above piece of code will immediately send an Ajax request after calculating the X/Y/Z coordinates of each tile, and convert the result into a JSON object, and send it back. Then we can add the Space ID of the soil liquefaction to the process code: ```javascript= var landLiquefactionSpaceId ='{SPACE_ID}'; ``` Then call "getGeoJSONTiles". Please modify the content in the "load" and "moveend" events: ```javascript= map.on('load', function () { getGeoJSONTiles(map.getBounds(), map.getZoom(), landLiquefactionSpaceId, dataHubReadToken, landLiquefactionFeatureGroup); }); map.on('moveend', function () { getGeoJSONTiles(map.getBounds(), map.getZoom(), landLiquefactionSpaceId, dataHubReadToken, landLiquefactionFeatureGroup); }); ``` Then add "landLiquefactionFeatureGroup" to the layer menu. ```javascript= var overlays = { 'Active fault': faultFeatureGroup, 'Soil Liquefaction': landLiquefactionFeatureGroup }; ``` After the test is completed, you can see that the content of the soil liquefaction can be displayed on the map, and a new copy will be downloaded every time the map is moved. ![](https://i.imgur.com/juHqFS2.gif) But the preset style is not what we want, so we have to define the style. Please continue to rewrite the ``getGeoJSONTiles'' function: ```javascript= function getGeoJSONTiles(bounds, zoom, spaceId, accessToken, featureGroup) { featureGroup.clearLayers(); var min = map.project(bounds.getNorthWest(), zoom).divideBy(256).floor(), max = map.project(bounds.getSouthEast(), zoom).divideBy(256).floor(); for (var i = min.x; i <= max.x; i++) { for (var j = min.y; j <= max.y; j++) { const coords = new L.Point(i, j); var x = coords.x, y = coords.y, z = zoom; $.getJSON('https://xyz.api.here.com/hub/spaces/' + spaceId +'/tile/web/' + z +'_' + x +'_' + y + '?access_token=' + accessToken, value => { value.features.forEach(element => { var geoJSONObject = L.geoJSON(element); if (element.properties['@ns:com:here:xyz'].space == landLiquefactionSpaceId) { // Use the space property in'@ns:com:here:xyz' in properties to compare whether it is the soil liquefaction layer we want // If yes, perform the following actions, use the "grading" attribute to fill in different colors switch (element.properties.classification) { case'low potential': geoJSONObject.setStyle({ color:'#26ff00', // green weight: 0 }); break; case'medium potential': geoJSONObject.setStyle({ color:'#ff9a03', // orange weight: 0 }); break; case'high potential': geoJSONObject.setStyle({ color:'#dd00ff', // purple weight: 0 }); break; } geoJSONObject.bindPopup('Soil liquefaction level: '+ element.properties.grading); geoJSONObject.addTo(featureGroup); } }); }) } } } ``` In this code, after we downloaded the data from the Data Hub, we added a comparison procedure to compare whether the data is the soil liquefaction layer we want, and if it is, fill it in according to the soil liquefaction classification. Into different colors. ![](https://i.imgur.com/wPYDmHh.png) It seems to be completed at present, but when we zoom the map, we will find some places are weird. For example, in this area of Yunlin Chiayi, the high-potential purple area has different shades! The reason is that when downloading using tile method, if a feature straddles different tiles, it will be downloaded repeatedly and drawn on the map, so we must add a parameter "clip=true" to eliminate this situation. please change this from: ```javascript= $.getJSON('https://xyz.api.here.com/hub/spaces/' + spaceId +'/tile/web/' + z +'_' + x +'_' + y +'?access_token ='+ accessToken ``` To: ```javascript= $.getJSON('https://xyz.api.here.com/hub/spaces/' + spaceId + '/tile/web/' + z + '_' + x + '_' + y + '?clip=true&access_token=' + accessToken ``` In this way, there is no difference in depth, and the addition of the soil liquefaction layer is now complete. ![](https://i.imgur.com/goCmswP.png) You can try to use the same method to add the layer of the forward slope, and roughly the map is complete! ![](https://i.imgur.com/e5Lbu6Q.png) ![](https://i.imgur.com/jNjaSMw.png) The full source code is as follows: ```javascript= <head> <meta http-equiv="content-type" content="text/html; charset=UTF-8" /> <!-- Load Leaflet from CDN --> <link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css" integrity="sha512-xodZBNTC5n17Xt2atTPuE1HxjVMSvLVW9ocqUKLsCC5CXdbqCmblAshOMAS6/keqq/sMZMZ19scR4PsZChSR7A==" crossorigin="" /> <!-- Make sure you put this AFTER Leaflet's CSS --> <script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js" integrity="sha512-XQoYMqMTK8LvdxXYG3nZ448hOEQiglfqkJs1NOQV44cWnUrBc8PkAOcXy20w0vlaXaVUearIOBhiXZ5V3ynxwA==" crossorigin=""></script> <script src="https://code.jquery.com/jquery-3.5.1.min.js" integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script> <style> #map { height: 100%; } </style> </head> <body> <div id="map"></div> </body> <script> var hereApiKey =''; // Your HERE APIKEY var dataHubReadToken =''; // Your Data Hub Token var faultSpaceId =''; // Active fault Space ID var landLiquefactionSpaceId =''; // Soil Liquefaction Space ID var dipSlopeSpaceId =''; // Downward slope Space ID var faultFeatureGroup = L.featureGroup(); //active fault layer var landLiquefactionFeatureGroup = L.featureGroup(); //Soil liquefaction layer var dipSlopeFeatureGroup = L.featureGroup(); //Downward slope layer var map = L.map('map'); // Create an L.map object. map.on('load', function () { getGeoJSONTiles(map.getBounds(), map.getZoom(), landLiquefactionSpaceId, dataHubReadToken, landLiquefactionFeatureGroup); getGeoJSONTiles(map.getBounds(), map.getZoom(), dipSlopeSpaceId, dataHubReadToken, dipSlopeFeatureGroup); faultFeatureGroup.addTo(map); landLiquefactionFeatureGroup.addTo(map); dipSlopeFeatureGroup.addTo(map); }); // Register the load event to monitor the first reading of the map map.on('moveend', function () { getGeoJSONTiles(map.getBounds(), map.getZoom(), landLiquefactionSpaceId, dataHubReadToken, landLiquefactionFeatureGroup); getGeoJSONTiles(map.getBounds(), map.getZoom(), dipSlopeSpaceId, dataHubReadToken, dipSlopeFeatureGroup); }); // Register the moveend event to monitor the end of each movement of the map map.setView([23.773, 120.959], 8); // Set the map position and Z level, and read the map var hereNormal = L.tileLayer( 'https://{s}.base.maps.ls.hereapi.com/maptile/2.1/maptile/newest/normal.day/{z}/{x}/{y}/256/png8?lg=cht&ppi =72&pois&apiKey=' + hereApiKey, { attribution:'© 2020 HERE', subdomains: [1, 2, 3, 4] }).addTo(map); // general map var hereHybrid = L.tileLayer( 'https://{s}.aerial.maps.ls.hereapi.com/maptile/2.1/maptile/newest/hybrid.day/{z}/{x}/{y}/256/png8?lg=cht&ppi =72&pois&apiKey=' + hereApiKey, { attribution:'© 2020 HERE', subdomains: [1, 2, 3, 4] }); var hereTerrain = L.tileLayer( 'https://{s}.aerial.maps.ls.hereapi.com/maptile/2.1/maptile/newest/terrain.day/{z}/{x}/{y}/256/png8?lg=cht&ppi =72&pois&apiKey=' + hereApiKey, { attribution:'© 2020 HERE', subdomains: [1, 2, 3, 4] }); var faultLayer = $.getJSON( 'https://xyz.api.here.com/hub/spaces/' + faultSpaceId +'/iterate?access_token=' + dataHubReadToken, value => { value.features.forEach(element => { L.geoJSON(element, { style: { color:'#0032ff', opacity: 0.8, weight: 8, fill: false } }).bindPopup(element.properties.Name).addTo(faultFeatureGroup); }); }); function getGeoJSONTiles(bounds, zoom, spaceId, accessToken, featureGroup) { var loadedTileIds = []; featureGroup.clearLayers(); var min = map.project(bounds.getNorthWest(), zoom).divideBy(256).floor(), max = map.project(bounds.getSouthEast(), zoom).divideBy(256).floor(); for (var i = min.x; i <= max.x; i++) { for (var j = min.y; j <= max.y; j++) { const coords = new L.Point(i, j); var x = coords.x, y = coords.y, z = zoom; $.getJSON('https://xyz.api.here.com/hub/spaces/' + spaceId +'/tile/web/' + z +'_' + x +'_' + y + '?clip=true&access_token=' + accessToken, value => { value.features.forEach(element => { if (!loadedTileIds.includes(element.id)) { // If id is not on the map, perform the following actions. var geoJSONObject = L.geoJSON(element); if (element.properties['@ns:com:here:xyz'].space == landLiquefactionSpaceId) { // Use the space property in'@ns:com:here:xyz' in properties to compare whether it is the soil liquefaction layer we want // If yes, perform the following actions, use the "grading" attribute to fill in different colors switch (element.properties.classification) { case'low potential': geoJSONObject.setStyle({ color:'#26ff00', // green weight: 0 }); break; case'medium potential': geoJSONObject.setStyle({ color:'#ff9a03', // orange weight: 0 }); break; case'high potential': geoJSONObject.setStyle({color:'#dd00ff', // purple weight: 0 }); break; } geoJSONObject.bindPopup('Soil liquefaction level: '+ element.properties.grading); geoJSONObject.addTo(featureGroup); } else if (element.properties['@ns:com:here:xyz'].space == dipSlopeSpaceId) { // Use the space property in'@ns:com:here:xyz' in properties to compare whether it is the forward slope layer we want // If yes, perform the following actions, use the "grading" attribute to fill in different colors geoJSONObject.setStyle({ color:'#ff0051', // red opacity: 0.3, weight: 1 }); geoJSONObject.bindPopup('Slope slope: '+ element.properties.SLOPE_ANG); geoJSONObject.addTo(featureGroup); } loadedTileIds.push(element.id); } }); }) } } } var baseLayers = { 'HERE Normal': hereNormal, 'HERE Hybrid': hereHybrid, 'HERE Terrain': hereTerrain }; var overlays = { 'Active fault': faultFeatureGroup, 'Soil Liquefaction': landLiquefactionFeatureGroup, 'Downward slope': dipSlopeFeatureGroup }; L.control.layers(baseLayers, overlays, { collapsed: false }).addTo(map); L.control.scale({ position:'bottomleft' }).addTo(map); </script> ```