# (5) - Leaflet JS integrates HERE location search API Most map service websites will have an input box for users to enter the target they want to search. After pressing "Enter", the search will be performed and the results will be displayed on the map. Do this part. ![](https://i.imgur.com/0iHKE0C.png) First, please add a div to the body and put an input (text input box) in it. ```javascript= <body> <div id="map"></div> <div id="searchbar" style=" position: absolute; top: 20px; z-index: 1000; left: 20px; "> <input id="inputbox" size="20"> </div> </body> ``` As a result, the input box is newly added, but it is also overlapped with the map zoom button. Let's slightly adjust the map layout: put the zoom button in the lower left corner and the scale bar in the lower right corner. First, add an option for the declaration of creating a map object, and turn off the default zoom button: ```javascript= var map = L.map('map', {zoomControl:false}); // 建立 L.map 物件。 ``` Then modify L.control.scale to the lower right corner, and add a L.control.zoom to the lower left corner: ```javascript= L.control.scale({ position:'bottomright' }).addTo(map); L.control.zoom({ position:'bottomleft' }).addTo(map); ``` ![](https://i.imgur.com/YCD9BdX.png) This feels much better, and then we are going to implement the search function. In addition to searching, what we want to implement also includes the function of searching suggestions. ## Get to know the HERE Geocoding and Search API The HERE Geocoding and Search API provides the function of searching addresses and locations. This API mainly provides several functions: 1. Discover: POI/Point of Interest query 2. Geocode: address query 3. Autosuggest: Provide suggestions based on the input string 4. Browse: set filter criteria for query 5. Lookup by ID: Use the location ID to query details 6. Reverse Geocode: Reverse search address or point of interest in latitude and longitude ###### Geocoding and Search API: https://developer.here.com/documentation/geocoding-search-api/ So the process we can plan out is: 1. The user enters a string. 2. The string entered by the user is sent to the Autosuggest function, and the suggestions are returned and displayed on the map. 3. If the user selects the suggested item, he will use the Lookup by ID function to query the details, and after obtaining the result, move the map and display it on the map. 4. If the user does not select the suggested item, but presses "Enter", a Discover query will be performed, and the result will be moved and displayed on the map. 5. If the user presses the right button on the map, the latitude and longitude on the map will be used to check the address and display it on the map. In this way, most of the functions are used. ## Implement Autosuggest function First, let’s take a look at Autosuggest. The URL of this api is: https://autosuggest.search.hereapi.com/v1/autosuggest? and accept these main parameters: 1. q: The input string. 2. at: Define the center point of a query, but do not set the radius. 3. in: Define the scope of a query, which can be a country or a defined circular or square area. 4. limit: limit the number of returned transactions. 5. lang: Define the language of the return. For example, English is en, and Taiwanese Chinese is zh-TW. 6. apikey: As mentioned in the previous course, HERE API uses apikey as authentication. Let's try it now. Set the location to Taipei City Government (25.0378862, 121.5645032), input the string to "Curry", set the language to Taiwanese Traditional Chinese, and limit the return of Wubi. The URL is: https://autosuggest.search.hereapi.com/v1/autosuggest?at=25.0378862,121.5645032&limit=5&lang=zh-TW&q=curry&apikey={API_KEY} The returned result is: ```json= {"items":[{"title":"Curry For Peace","id":"here:pds:place:158wsqqq-753e60497f6b4d99a4c3362386246448","resultType":"place","address":{"label":"Curry For Peace, No. 7, 光復南路519巷, 信義區, 台北市, 110, 台灣"},"position":{"lat":25.03194,"lng":121.5578},"access":[{"lat":25.03177,"lng":121.5578}],"distance":945,"categories":[{"id":"100-1000-0001","name":"休閒餐飲","primary":true},{"id":"100-1000-0000","name":"餐廳"}],"references":[{"supplier":{"id":"yelp"},"id":"gJA2AzjPqiZwdx6B8AKHpA"}],"foodTypes":[{"id":"200-000","name":"亞洲","primary":true},{"id":"203-000","name":"日式"}],"highlights":{"title":[{"start":0,"end":5}],"address":{"label":[{"start":0,"end":5}]}}},{"title":"Au Curry","id":"here:pds:place:158aabd1-45b654eefb980234ee1c82bfc8427b80","resultType":"place","address":{"label":"台灣110台北市信義區松高路12號Au Curry"},"position":{"lat":25.03896,"lng":121.5674},"access":[{"lat":25.03908,"lng":121.56739}],"distance":315,"categories":[{"id":"100-1000-0000","name":"餐廳","primary":true}],"references":[{"supplier":{"id":"tripadvisor"},"id":"6152706"}],"foodTypes":[{"id":"203-000","name":"日式","primary":true},{"id":"202-000","name":"印度"}],"highlights":{"title":[],"address":{"label":[]}}},{"title":"Curry Stand 1983","id":"here:pds:place:158wsqqt-a328e65232a348fd83a9c01df7b2a60d","resultType":"place","address":{"label":"Curry Stand 1983, No. 16, 敦化北路222巷, 松山區, 台北市, 105, 台灣"},"position":{"lat":25.05931,"lng":121.54795},"access":[{"lat":25.05937,"lng":121.54801}],"distance":2907,"categories":[{"id":"600-6900-0000","name":"日常必需品","primary":true}],"highlights":{"title":[{"start":0,"end":5}],"address":{"label":[{"start":0,"end":5}]}}},{"title":"Curry Lab. Tokyo","id":"here:pds:place:158wsqqq-1139264b8d8c4ee4956d502205dd108b","resultType":"place","address":{"label":"Curry Lab. Tokyo, No. 384, 莊敬路, 信義區, 台北市, 110, 台灣"},"position":{"lat":25.02744,"lng":121.56527},"access":[{"lat":25.02756,"lng":121.56531}],"distance":1165,"categories":[{"id":"100-1000-0000","name":"餐廳","primary":true}],"foodTypes":[{"id":"203-000","name":"日式","primary":true}],"highlights":{"title":[{"start":0,"end":5}],"address":{"label":[{"start":0,"end":5}]}}},{"title":"金華街咖哩屋 (Curry House)","id":"here:pds:place:158wsqqm-081c6c6eb98742e588da71d9b38836a9","resultType":"place","address":{"label":"金華街咖哩屋, No. 4, 金華街199巷1弄, 大安區, 台北市, 106, 台灣"},"position":{"lat":25.03025,"lng":121.52824},"access":[{"lat":25.03028,"lng":121.52824}],"distance":3751,"categories":[{"id":"100-1000-0000","name":"餐廳","primary":true}],"references":[{"supplier":{"id":"core"},"id":"1010554820"},{"supplier":{"id":"tripadvisor"},"id":"7767317"},{"supplier":{"id":"yelp"},"id":"AmJy3o30qkI8EbQOFipjSQ"}],"foodTypes":[{"id":"202-000","name":"印度","primary":true},{"id":"200-000","name":"亞洲"},{"id":"201-000","name":"中式"},{"id":"206-000","name":"越南"}],"highlights":{"title":[{"start":8,"end":13}],"address":{"label":[]}}}],"queryTerms":[]} } ``` Let's start with the Autosuggest function. We add the search function to the map. We are going to use a very convenient jQuery plug-in: EasyAutocomplete. ###### EasyAutocomplete official website: http://easyautocomplete.com/ First, we first add EasyAutocomplete's JS and CSS to the head. ```javascript= <script src="https://cdnjs.cloudflare.com/ajax/libs/easy-autocomplete/1.3.5/jquery.easy-autocomplete.min.js" integrity="sha512-Z/2pIbAzFuLlc7WIt/xifag7As7GuTqoBbLsVTgut69QynAIOclmweT6o7pkxVoGGfLcmPJKn/lnxyMNKBAKgg==" crossorigin="anonymous"></script> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/easy-autocomplete/1.3.5/easy-autocomplete.min.css" integrity="sha512-TsNN9S3X3jnaUdLd+JpyR5yVSBvW9M6ruKKqJl5XiBpuzzyIMcBavigTAHaH50MJudhv5XIkXMOwBL7TbhXThQ==" crossorigin="anonymous" /> ``` Then add this code: ```javascript= var options = {// Define the source of selected items for EasyAutocomplete url: function (phrase) { return'https://autosuggest.search.hereapi.com/v1/autosuggest?' + // Autosuggest API URL 'q=' + phrase + // Receive the string entered by the user to search '&limit=5' + // Limit five returns at most '&lang=zh_TW' + // Limit Taiwanese Traditional Chinese '&at=' + map.getCenter().lat +',' + map.getCenter().lng + // Use the center point of the current map as the starting point for the search '&apikey=' + hereApiKey; // Your HERE API KEY }, listLocation:'items', // use the returned item as the selection list getValue:'title', // Show title in the selection list list: { onClickEvent: function () {// Action after pressing the selected item var data = $("#inputbox").getSelectedItemData(); var northWest = L.latLng(data.mapView.north, data.mapView.west), // select the northwest corner of the project southEast = L.latLng(data.mapView.south, data.mapView.east); // select the southeast corner of the project map.fitBounds([northWest, southEast]); // Move the map to the selected item } }, requestDelay: 100, // Delay 100 milliseconds before sending the request placeholder:'Search location' // string displayed by default }; $('#inputbox').easyAutocomplete(options); // enable EasyAutocomplete to inpupbox this component ``` After the addition is complete, we can start the test. If it is successful, it will be like this. Enter a string and get suggestions. After clicking the suggested item, the map will be moved to the location of the item: ![](https://i.imgur.com/RZHPhGj.gif) ## Implement the Discover function But if the user does not select the suggested item, but directly presses "Enter", what should I do? We will use another function: Discover, which is to search for locations. The API URL of Discover is: https://discover.search.hereapi.com/v1/discover? And the commonly used parameters are: 1. q: The input string. 2. at: Define the center point of a query, but do not set the radius. 3. in: Define the scope of a query, which can be a country or a defined circular or square area. 4. limit: limit the number of returned transactions. 5. lang: Define the language of the return. For example, English is en, and Taiwanese Chinese is zh-TW. 6. apikey: As mentioned in the previous course, HERE API uses apikey as authentication. In fact, it is similar to the parameters received by Autosuggest. Let's try it now. Set the location to Taipei City Hall (25.0378862, 121.5645032), input the string to "Curry", set the language to Taiwanese Traditional Chinese, and limit the return of Wubi. The URL is: https://discover.search.hereapi.com/v1/discover?at=25.0378862,121.5645032&limit=5&lang=zh-TW&q=curry&apikey={API_KEY} The data returned is as follows: ```json= {"items":[{"title":"Au Curry","id":"here:pds:place:158aabd1-45b654eefb980234ee1c82bfc8427b80","resultType":"place","address":{"label":"台灣110台北市信義區松高路12號Au Curry","countryCode":"TWN","countryName":"台灣","county":"台北市","city":"台北市","district":"信義區","street":"松高路","postalCode":"110","houseNumber":"12"},"position":{"lat":25.03896,"lng":121.5674},"access":[{"lat":25.03908,"lng":121.56739}],"distance":315,"categories":[{"id":"100-1000-0000","name":"餐廳","primary":true}],"references":[{"supplier":{"id":"tripadvisor"},"id":"6152706"}],"foodTypes":[{"id":"203-000","name":"日式","primary":true},{"id":"202-000","name":"印度"}],"contacts":[{"phone":[{"value":"+886287895141"}]}]},{"title":"Namaste塔美爾異國風","id":"here:pds:place:158jx7ps-76ab0942f18a0a2dfdb7aa43451422d5","resultType":"place","address":{"label":"Namaste塔美爾異國風, No. 16, 羅斯福路三段316巷, 中正區, 台北市, 100, 台灣","countryCode":"TWN","countryName":"台灣","county":"台北市","city":"台北市","district":"中正區","street":"羅斯福路三段316巷","postalCode":"100","houseNumber":"16"},"position":{"lat":25.01547,"lng":121.53224},"access":[{"lat":25.01552,"lng":121.53217}],"distance":4096,"categories":[{"id":"100-1000-0000","name":"餐廳","primary":true},{"id":"100-1000-0001","name":"休閒餐飲"}],"references":[{"supplier":{"id":"core"},"id":"1112532981"},{"supplier":{"id":"core"},"id":"1119347069"},{"supplier":{"id":"tripadvisor"},"id":"6133832"},{"supplier":{"id":"yelp"},"id":"x89WRDiBgVFhVPt0X6kABw"}],"foodTypes":[{"id":"204-000","name":"東南亞","primary":true},{"id":"200-000","name":"亞洲"},{"id":"202-000","name":"印度"},{"id":"259-000","name":"西藏"}],"contacts":[{"phone":[{"value":"+886223629538"}],"www":[{"value":"http://namastecurry.qrweb.com.tw"}]}],"openingHours":[{"categories":[{"id":"100-1000-0000"}],"text":["週二-週五: 11:30 - 14:00, 17:00 - 21:00","週六, 週日: 09:00 - 17:00"],"isOpen":false,"structured":[{"start":"T113000","duration":"PT02H30M","recurrence":"FREQ:DAILY;BYDAY:TU,WE,TH,FR"},{"start":"T170000","duration":"PT04H00M","recurrence":"FREQ:DAILY;BYDAY:TU,WE,TH,FR"},{"start":"T090000","duration":"PT08H00M","recurrence":"FREQ:DAILY;BYDAY:SA,SU"}]}]},{"title":"金華街咖哩屋 (Curry House)","id":"here:pds:place:158wsqqm-081c6c6eb98742e588da71d9b38836a9","resultType":"place","address":{"label":"金華街咖哩屋, No. 4, 金華街199巷1弄, 大安區, 台北市, 106, 台灣","countryCode":"TWN","countryName":"台灣","county":"台北市","city":"台北市","district":"大安區","street":"金華街199巷1弄","postalCode":"106","houseNumber":"4"},"position":{"lat":25.03025,"lng":121.52824},"access":[{"lat":25.03028,"lng":121.52824}],"distance":3751,"categories":[{"id":"100-1000-0000","name":"餐廳","primary":true}],"references":[{"supplier":{"id":"core"},"id":"1010554820"},{"supplier":{"id":"tripadvisor"},"id":"7767317"},{"supplier":{"id":"yelp"},"id":"AmJy3o30qkI8EbQOFipjSQ"}],"foodTypes":[{"id":"202-000","name":"印度","primary":true},{"id":"200-000","name":"亞洲"},{"id":"201-000","name":"中式"},{"id":"206-000","name":"越南"}],"contacts":[{"phone":[{"value":"+886223411371"}]}],"openingHours":[{"text":["週一, 週三-週日: 11:30 - 14:30, 17:30 - 21:30"],"isOpen":false,"structured":[{"start":"T113000","duration":"PT03H00M","recurrence":"FREQ:DAILY;BYDAY:MO,WE,TH,FR,SA,SU"},{"start":"T173000","duration":"PT04H00M","recurrence":"FREQ:DAILY;BYDAY:MO,WE,TH,FR,SA,SU"}]}]},{"title":"Curry For Peace","id":"here:pds:place:158wsqqq-753e60497f6b4d99a4c3362386246448","resultType":"place","address":{"label":"Curry For Peace, No. 7, 光復南路519巷, 信義區, 台北市, 110, 台灣","countryCode":"TWN","countryName":"台灣","county":"台北市","city":"台北市","district":"信義區","street":"光復南路519巷","postalCode":"110","houseNumber":"7"},"position":{"lat":25.03194,"lng":121.5578},"access":[{"lat":25.03177,"lng":121.5578}],"distance":945,"categories":[{"id":"100-1000-0001","name":"休閒餐飲","primary":true},{"id":"100-1000-0000","name":"餐廳"}],"references":[{"supplier":{"id":"yelp"},"id":"gJA2AzjPqiZwdx6B8AKHpA"}],"foodTypes":[{"id":"200-000","name":"亞洲","primary":true},{"id":"203-000","name":"日式"}],"contacts":[{"phone":[{"value":"+886287869877"}]}],"openingHours":[{"categories":[{"id":"100-1000-0001"}],"text":["週一-週六: 11:00 - 21:00"],"isOpen":true,"structured":[{"start":"T110000","duration":"PT10H00M","recurrence":"FREQ:DAILY;BYDAY:MO,TU,WE,TH,FR,SA"}]}]},{"title":"咖哩廚房 (Curry Kitchen咖哩廚房)","id":"here:pds:place:158jx7ps-02cef1e540c5034d2f818a57306ed577","resultType":"place","address":{"label":"咖哩廚房, No. 61-1, 和平東路二段118巷, 大安區, 台北市, 106, 台灣","countryCode":"TWN","countryName":"台灣","county":"台北市","city":"台北市","district":"大安區","street":"和平東路二段118巷","postalCode":"106","houseNumber":"61-1"},"position":{"lat":25.02227,"lng":121.54292},"access":[{"lat":25.02227,"lng":121.54284}],"distance":2783,"categories":[{"id":"100-1000-0000","name":"餐廳","primary":true}],"references":[{"supplier":{"id":"core"},"id":"1122381606"},{"supplier":{"id":"yelp"},"id":"786oaQdHN7R8G6oJBIkLEw"}],"foodTypes":[{"id":"204-000","name":"東南亞","primary":true},{"id":"202-000","name":"印度"},{"id":"203-000","name":"日式"},{"id":"211-000","name":"印尼"}],"contacts":[{"phone":[{"value":"+886227388486"}]}],"openingHours":[{"text":["週一-週日: 11:30 - 14:00, 17:00 - 20:30"],"isOpen":false,"structured":[{"start":"T113000","duration":"PT02H30M","recurrence":"FREQ:DAILY;BYDAY:MO,TU,WE,TH,FR,SA,SU"},{"start":"T170000","duration":"PT03H30M","recurrence":"FREQ:DAILY;BYDAY:MO,TU,WE,TH,FR,SA,SU"}]}]}]} ``` 為了要實做按下「Enter」進行搜尋的功能,請加入以下的程式碼: ```javascript= $('#inputbox').on('keypress', function (e) { if (e.which == 13) { // 監聽使用者是否按下「Enter」 var phrase = $('#inputbox').val(); // 取得使用者輸入的字串 $.getJSON('https://discover.search.hereapi.com/v1/discover?' + // Discover 的 API URL 'q=' + phrase + // 接收使用者輸入的字串做搜尋 '&limit=1' + // 最多限定一筆回傳 '&lang=zh-TW' + // 限定台灣正體中文 '&at=' + map.getCenter().lat + ',' + map.getCenter().lng + // 使用目前地圖的中心點作為搜尋起始點 '&apikey=' + hereApiKey, value => { value.items.forEach(data => { if (data.mapView) { // 如果回傳的是地址,就進行這個動作 var northWest = L.latLng(data.mapView.north, data.mapView .west), // 選取項目的西北角 southEast = L.latLng(data.mapView.south, data.mapView .east); // 選取項目的東南角 map.flyToBounds([northWest, southEast]); // 把地圖移動到選取項目 } else if (data.position) { // 如果回傳的是興趣點,就進行這個動作 map.flyTo(L.latLng(data.position), 16); // 把地圖移到選取項目的地點 } }) }) } }); ``` Then we can test the effect of directly pressing "Enter" without selecting the suggested item after inputting the string: ![](https://i.imgur.com/EFqkdiE.gif) In this way, the basic search function is completed. We have combined two APIs: Autosuggest and Discover. Next, we need to find a way to know whether the target we are searching for is near a soil liquefaction zone, a forward slope or a fault zone. Here we need to use a latitude and longitude to search for these three Spaces. ## Combine search function and Data Hub query function According to the previous lesson, we can use the center point plus the radius to perform a geographic search. For example, we tried this search (query Taipei Main Station (Longitude: 121.5170534/Latitude: 25.0478554) around one kilometer (1000 meters) ) All medical institutions within): https://xyz.api.here.com/hub/spaces/{SPACE_ID}/spatial?lon=121.5170534&lat=25.0478554&radius=1000&access_token={TOKEN} We can apply the same method to the three spaces of soil liquefaction zone, forward slope and fault zone. Replace the SPACE_ID of the above query with the Space ID of the soil liquefaction zone, and query the surrounding area of ​​Taipei Main Station, but change the radius to 10 meters, and you will get the following result: ![](https://i.imgur.com/p4ftQxY.png) You can see that the information returned is that Taipei Main Station is actually located in a potential area of ​​soil liquefaction, so we can integrate this search on the map, using the latitude and longitude we just obtained with the results of Autosuggest or Discover. Let's first add the following function "dataHubSpatialSearch" to the source code of the webpage. This function receives latitude, longitude, radius, Space ID and Data Hub Token. It is not difficult to see that this is used to search the content on the Data Hub. ```javascript= function dataHubSpatialSearch(lat, lng, radius, spaceId, accessToken) { return new Promise(function (resolve, reject) { var url ='https://xyz.api.here.com/hub/spaces/' + spaceId +'/spatial?lon=' + lng + '&lat=' + lat +'&radius=' + radius +'&access_token=' + accessToken; $.getJSON(url, value => { var result; if (value.features.length> 0) { result = value.features[0]; // If the result is found, return the first Feature } else { result = null; // If the result is not found, return null } resolve(result); // Pass the returned result to the next step }) }); } ``` Next, we first create a variable, which will hold a map marker afterwards, but leave it blank for now. ```javascript= var mapResultMarker; ``` After that, add the following function "getDataHubResults" on the map. After receiving the latitude and longitude, the three Spaces of the soil liquefaction zone, the forward slope and the fault zone will be queried respectively. Because it is an asynchronous request, it will wait for the previous query to find the result. Only proceed to the next time. After the last three queries are completed, create a new L.marker, add L.popup to display the text, and finally add it to the map. As for the label parameter, it is used to display the address or place name. ```javascript= function getDataHubResults(lat, lng, label) { if (mapResultMarker) { mapResultMarker.remove(); // If there is already a marker on the map, remove it from the map } var landLiquefactionInfo ='Soil Liquefaction: None', faultInfo ='Active fault: None', dipSlopeInfo ='Slope Slope: None'; // First define if the result is not found, it will display "None". dataHubSpatialSearch(lat, lng, 100, landLiquefactionSpaceId, dataHubReadToken).then( function onFulfilled( // Query whether there is a soil liquefaction zone within 100 meters, if there is a soil liquefaction zone, display it result) { if (result) { landLiquefactionInfo ='Soil liquefaction:' + result.properties. classification; } dataHubSpatialSearch(lat, lng, 100, dipSlopeSpaceId, dataHubReadToken).then( function onFulfilled( // Query whether there is a forward slope within 100 meters, and display it if there is result) { if (result) { dipSlopeInfo ='Slope slope: slope' + result.properties.SLOPE_ANG +'degree' } dataHubSpatialSearch(lat, lng, 20000, faultSpaceId, dataHubReadToken).then( function onFulfilled( // Query whether there is an active fault within 20 kilometers, and display it if there is any result) { if (result) { faultInfo ='Active fault:' + result.properties.Name; } mapResultMarker = L.marker(L.latLng(lat, lng)).bindPopup(label +'</br>' + landLiquefactionInfo +'</br>' + dipSlopeInfo +'</br>' + faultInfo ); // define a new markerp mapResultMarker.addTo(map); // add the marker to the map mapResultMarker.openPopup(); }) }) }) } ``` Then, every time we get the results of the location query, whether we use Autosuggest to get suggestions or directly press "Enter" to search, we will call "getDataHubResults" to search after the results are obtained. Just add the call under "map.flyToBounds()" and "map.flyTo()". The final effect will look like this! ![](https://i.imgur.com/kR9cxaC.gif) ## Realize the latitude and longitude reverse check function Leaflet JS provides many object interaction and event notification/monitoring functions. What we want to do here is to right-click on the map, and then we can return the address of this place and the Data Hub layer related to the query in the previous part. . The API we are going to use here is Reverse Geocode, which is usually called "reverse address". The URL of the HERE Reverse Geocode API is: https://revgeocode.search.hereapi.com/v1/revgeocode? and accepts the following parameters: 1. at: latitude and longitude position. 2. limit: the upper limit of the number of returned data. 3. limit: limit the number of returned transactions. 4. lang: Define the language of the return. For example, English is en, and Taiwanese Chinese is zh-TW. For example, we can use the latitude and longitude of Taipei Main Station to see what information will be returned: https://revgeocode.search.hereapi.com/v1/revgeocode?at=25.0478554,121.5170534&limit=1&lang=zh-TW&apikey={API_KEY} ```json= { "items": [ { "title": "台北火車站", "id": "here:pds:place:158wsqqm-97384e3eacc34dbdaf32a6bf341e50c9", "resultType": "place", "address": { "label": "台灣100台北市中正區台北火車站", "countryCode": "TWN", "countryName": "台灣", "county": "台北市", "city": "台北市", "district": "中正區", "postalCode": "100" }, "position": { "lat": 25.04775, "lng": 121.51716 }, "access": [ { "lat": 25.04828, "lng": 121.51725 } ], "distance": 17, "categories": [ { "id": "400-4100-0042", "name": "巴士車站", "primary": true } ] } ] } ``` Well, it did return "Taipei Railway Station". Although it was not a return address, it was far from satisfactory. Leaflet LS monitors the event that the user presses the right mouse button on the map is "contextmenu", so I can call it in a very simple way. Please add the following code to the page: ```javascript= map.on('contextmenu', event => { console.log(event); // display the event in the console }) ``` Open the console and press the right button on the map to display the content of this event. You can see that it contains a latlng attribute, which is the latitude and longitude information we want. ![](https://i.imgur.com/lJB5kvX.png) After getting the latitude and longitude information, we can start development. In fact, the method is similar to the location query, which is to put the latitude and longitude into the query url to call the Reverse Geocode API, receive the returned result and query the Data Hub, and finally display it on the map. Modify the source code just now: ```javascript= map.on('contextmenu', event => { if (mapResultMarker) { mapResultMarker.remove(); // If there is already a marker on the map, remove it from the map } var reverseGeocodeUrl ='https://revgeocode.search.hereapi.com/v1/revgeocode?at=' + event.latlng.lat + ',' + event.latlng.lng +'&limit=1&lang=zh-TW&apikey=' + hereApiKey $.getJSON(reverseGeocodeUrl, value => { if (value.items.length> 0) { getDataHubResults(event.latlng.lat, event.latlng.lng, value.items[0].title); } }) }) ``` Plus another event monitor, that is, if you click the left mouse button on the map, the marker will be removed. ```javascript= map.on('click', () => { if (mapResultMarker) { mapResultMarker.remove(); // If there is already a marker on the map, remove it from the map } }) ``` After completion, press the right button on the map to perform Reverse Geocode and query the contents of the Data Hub, and finally display the result on the map. ![](https://i.imgur.com/fHzeh28.png) Our residential safety map is almost finished here, but in the process of using it, you will definitely find some problems, such as: 1. If there are several active faults in the search range, it seems that the closest one may not be displayed? 2. The line of the active fault is covered by the other two layers. How to adjust the order of the layers? 3. How to increase fluency? Reduce repeated downloads? These are left to you to think about how to solve it! But in fact, you can find solutions by searching on the Internet, just to actually make them out. You can also think about any new features or data sets that can be improved or added. Perhaps you can also post more interesting new map applications. Welcome to the HERE developer website to explore more HERE Map API/SDK How to use it. ###### HERE developer website: https://developer.here.com/ The complete source code of this project 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> <script src="https://cdnjs.cloudflare.com/ajax/libs/easy-autocomplete/1.3.5/jquery.easy-autocomplete.min.js" integrity="sha512-Z/2pIbAzFuLlc7WIt/xifag7As7GuTqoBbLsVTgut69QynAIOclmweT6o7pkxVoGGfLcmPJKn/lnxyMNKBAKgg==" crossorigin="anonymous"></script> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/easy-autocomplete/1.3.5/easy-autocomplete.min.css" integrity="sha512-TsNN9S3X3jnaUdLd+JpyR5yVSBvW9M6ruKKqJl5XiBpuzzyIMcBavigTAHaH50MJudhv5XIkXMOwBL7TbhXThQ==" crossorigin="anonymous" /> <style> #map { height: 100%; } </style> </head> <body> <div id="map"></div> <div id="searchbar" style=" position: absolute; top: 20px; z-index: 1000; left: 20px; "> <input id="inputbox" size="20"> </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', { zoomControl: false }); // Create 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 + '?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 grade:' + 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('Downward slope slope:' + element.properties.SLOPE_ANG); geoJSONObject.addTo(featureGroup); } loadedTileIds.push(element.id); } }); }) } } } var baseLayers = { 'HERE Standard Map': hereNormal, 'HERE Satellite Image': hereHybrid, 'HERE topographic map': 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:'bottomright' }).addTo(map); L.control.zoom({ position:'bottomleft' }).addTo(map); function dataHubSpatialSearch(lat, lng, radius, spaceId, accessToken) { return new Promise(function (resolve, reject) { var url ='https://xyz.api.here.com/hub/spaces/' + spaceId +'/spatial?lon=' + lng + '&lat=' + lat +'&radius=' + radius +'&access_token=' + accessToken; $.getJSON(url, value => { var result; if (value.features.length> 0) { result = value.features[0]; // If the result is found, return the first Feature } else { result = null; // If the result is not found, return null } resolve(result); // Pass the returned result to the next step }) }); } var mapResultMarker; function getDataHubResults(lat, lng, label) { if (mapResultMarker) { mapResultMarker.remove(); // If there is already a marker on the map, remove it from the map } var landLiquefactionInfo ='Soil Liquefaction: None', faultInfo ='Active fault: None', dipSlopeInfo ='Slope Slope: None'; // First define if the result is not found, it will display "None". dataHubSpatialSearch(lat, lng, 100, landLiquefactionSpaceId, dataHubReadToken).then( function onFulfilled( // Query whether there is a soil liquefaction zone within 100 meters, if there is a soil liquefaction zone, display it result) { if (result) { landLiquefactionInfo ='Soil liquefaction:' + result.properties. classification; } dataHubSpatialSearch(lat, lng, 100, dipSlopeSpaceId, dataHubReadToken).then( function onFulfilled( // Query whether there is a forward slope within 100 meters, and display it if there is result) { if (result) { dipSlopeInfo ='Slope slope: slope' + result.properties.SLOPE_ANG +'degree' } dataHubSpatialSearch(lat, lng, 20000, faultSpaceId, dataHubReadToken).then( function onFulfilled( // Query whether there is an active fault within 20 kilometers, and display it if there is any result) { if (result) { faultInfo ='Active fault:' + result.properties.Name; } mapResultMarker = L.marker(L.latLng(lat, lng)).bindPopup(label +'</br>' + landLiquefactionInfo +'</br>' + dipSlopeInfo +'</br>' + faultInfo ); // define a new markerp mapResultMarker.addTo(map); // add the marker to the map mapResultMarker.openPopup(); }) }) }) } var options = {// Define the source of selected items for EasyAutocomplete url: function (phrase) { return'https://autosuggest.search.hereapi.com/v1/autosuggest?' + // Autosuggest API URL 'q=' + phrase + // Receive the string entered by the user to search '&limit=10' + // Limit five returns at most '&lang=zh-TW' + // Limit Taiwanese Traditional Chinese '&at=' + map.getCenter().lat +',' + map.getCenter().lng + // Use the center point of the current map as the starting point for the search '&apikey=' + hereApiKey; // Your HERE API KEY }, listLocation:'items', // use the returned items as the selection list getValue: function (element) { if (element.mapView || element.position) { return element.title; } else { return''; } }, // Show title in the selection list list: { onClickEvent: function () {// Action after pressing the selected item var data = $("#inputbox").getSelectedItemData(); if (data.mapView) {// If the address is returned, do this var northWest = L.latLng(data.mapView.north, data.mapView.west), // select the northwest corner of the project southEast = L.latLng(data.mapView.south, data.mapView.east); // select the southeast corner of the project map.flyToBounds([northWest, southEast]); // Move the map to the selected item getDataHubResults(data.position.lat, data.position.lng, data.title); } else if (data.position) {// If the return is a point of interest, perform this action map.flyTo(L.latLng(data.position), 16); // Move the map to the location of the selected item getDataHubResults(data.position.lat, data.position.lng, data.title); } } }, requestDelay: 100, // Delay 100 milliseconds before sending the request placeholder:'Search location' // string displayed by default }; $('#inputbox').easyAutocomplete(options); // enable EasyAutocomplete to inpupbox this component $('#inputbox').on('keypress', function (e) { if (e.which == 13) {// monitor whether the user presses "Enter" var phrase = $('#inputbox').val(); // Get the string entered by the user $.getJSON('https://discover.search.hereapi.com/v1/discover?' + // Discover API URL 'q=' + phrase + // Receive the string entered by the user to search '&limit=1' + // Limit one return at most '&lang=zh-TW' + // Limit Taiwanese Traditional Chinese '&at=' + map.getCenter().lat +',' + map.getCenter().lng + // Use the center point of the current map as the starting point for the search '&apikey=' + hereApiKey, value => { value.items.forEach(data => { if (data.mapView) {// If the address is returned, do this var northWest = L.latLng(data.mapView.north, data.mapView .west), // select the northwest corner of the project southEast = L.latLng(data.mapView.south, data.mapView .east); // select the southeast corner of the project map.flyToBounds([northWest, southEast]); // Move the map to the selected item getDataHubResults(data.position.lat, data.position.lng, data.title); } else if (data.position) {// If the return is a point of interest, perform this action map.flyTo(L.latLng(data.position), 16); // Move the map to the location of the selected item getDataHubResults(data.position.lat, data.position.lng, data.title); } }) }) } }); map.on('contextmenu', event => { if (mapResultMarker) { mapResultMarker.remove(); // If there is already a marker on the map, remove it from the map } var reverseGeocodeUrl ='https://revgeocode.search.hereapi.com/v1/revgeocode?at=' + event.latlng.lat + ',' + event.latlng.lng +'&limit=1&lang=zh-TW&apikey=' + hereApiKey $.getJSON(reverseGeocodeUrl, value => { if (value.items.length> 0) { getDataHubResults(event.latlng.lat, event.latlng.lng, value.items[0].title); } }) }) map.on('click', () => { if (mapResultMarker) { mapResultMarker.remove(); // If there is already a marker on the map, remove it from the map } }) </script> ``` --- 1. 什麼是 HERE Studio/Data Hub 2. 五分鐘內製作第一份 HERE Studio 地圖 3. 進階功能:認識 HERE CLI、Data Hub API 與 Data Hub Console 5. 使用 QGIS 玩轉 HERE Studio/Datahub 6. 使用 QGIS 建立靜態社群地圖(北市禁停紅黃線地圖) 7. 使用 Leaflet JS 與 Datahub API 建立您的網路地圖(居住安全地圖) 帳號:uso03023@cuoly.com 密碼:88888888 最高權限:ACsefqyqTJmy8Fs1IL6ONwA Token: AAtL2uUeSTi0h7976-nBUAA 土壤液化潛勢圖資群組: pa76VKIl 活動斷層分佈圖: y0wpTUHG 順向坡分佈圖: ev9DTXqB * 口罩地圖(csv): https://scidm.nchc.org.tw/dataset/nhi-maskdata * 臺北市禁停紅黃線地圖(shp):https://data.taipei/#/dataset/detail?id=a9c282c5-3a43-41ed-badc-f9c1bdf1cc34 * 臺北市區界圖:https://data.taipei/#/dataset/detail?id=1601ef3a-c253-4988-b047-943d9e786143 * 順向坡地圖(geojson):https://opendata.epa.gov.tw/Data/Details/SOIL00112 * 活動斷層分佈圖(kml):https://opendata.epa.gov.tw/Data/Details/SOIL00103 * 土壤液化潛勢圖資群組(geojson):https://data.gov.tw/dataset/28691