How do I create Boundary Polygons using Data-driven Styling, after PlaceFeature.displayName has been deprecated? - JavaScript Google Maps API - javascript

I have a website that uses Google Maps API to create a map, with one random country being highlighted at a time. The way I currently highlight a country is using data-driven styling to create boundary polygons:
Example of how the map should look
But, Google recently started throwing errors, saying that the displayName property (which I use) could no longer be used as of February 2023. Here's a snippet of how my code looked before the change:
featureLayer.style = (options) => {
if (options.feature["displayName"] == "Italy") {
return featureStyleOptions;
}
};
In the official explanation, the fetchPlace() function should just be used as a replacement, because the fetchPlace() function also returns the displayName.
But fetchPlace() can't be used in a synchronous function (it only returns promises), so I tried rewriting the code. In this attempt I used the .then() method, but now it doesn't apply the boundary polygons at all:
featureLayer.style = (options) => {
options.feature.fetchPlace().then((Place) => {
if (Place["displayName"] == "Italy") {
return featureStyleOptions;
}
});
};
I only have limited knowledge of how promises/.then() work in JavaScript and how they handle the values, so I might be completely wrong with this approach.
Ideally I would just be using the PlaceId as a replacement for DisplayName, but I don't have any way of obtaining the IDs. I will appreciate any kind of help with this problem. Thank you!

displayName is deprecated but not the placeId.
Below is an example highlighting Italy. To get the Place ID, you can use the Place ID finder. I have added a call to the Geocoder to be able to fit the map to the feature.
let map;
let featureLayer;
function initMap() {
let geocoder = new google.maps.Geocoder();
map = new google.maps.Map(document.getElementById("map"), {
center: {
lat: 0,
lng: 0
},
zoom: 13,
mapId: "8ec1ff859561face", // You need to create your own map ID
});
featureLayer = map.getFeatureLayer("COUNTRY");
// Define the styling options
const featureStyleOptions = {
strokeColor: "#00FF00",
strokeOpacity: 1,
strokeWeight: 1,
fillColor: '#519400',
fillOpacity: 1,
};
// Below Place ID is "Italy"
let placeId = 'ChIJA9KNRIL-1BIRb15jJFz1LOI';
geocoder.geocode({
placeId: placeId
})
.then(({
results
}) => {
map.fitBounds(results[0].geometry.viewport);
})
.catch((e) => {
console.log('Geocoder failed due to: ' + e)
});
// Apply the style to a single boundary.
featureLayer.style = (options) => {
if (options.feature.placeId == placeId) {
return featureStyleOptions;
}
};
}
#map {
height: 180px;
}
<div id="map"></div>
<script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyCkUOdZ5y7hMm0yrcCQoCvLwzdM6M8s5qk&callback=initMap&v=beta" defer></script>
Regarding fetchPlace(), the documentation states:
Do not call this from a FeatureStyleFunction since only synchronous FeatureStyleFunctions are supported.

Related

Unable to apply custom icons for clustering in google maps markerclusterer because unable to provide position data

The documentation for this utility make no sense to me. According to this and this, you can customise cluster icons by first converting the cluster icon to a marker via the MarkerClusterer's renderer property, and then apply icon styles as you would a normal marker. However, a new google.maps.Marker requires a position property to work - a ``property I don't have access to. I have access to the individual positions of individual marker locations, but the whole point of using the marker clustering functionality is that is calculates a mid-point for you.
How can I render the cluster in the correct position, if I don't know the position? I can access a _position property on the stats prop, but that throws an error:
Cannot read properties of undefined (reading 'addListener')
I'm really lost as to what to do here, because there don't seem to be many reliable examples out there. As far as I can tell, I'm following the example set out in their github.
private createClusteredMarkerMap() {
new this._GoogleMaps.load((google) => {
let map = new google.maps.Map(this._map, mapOptions);
const markers = this._locations.map(({ lat, long }) => {
// loop and extract lat/lng
});
new markerClusterer.MarkerClusterer({
map,
markers,
renderer: {
render: (clusters, stats) => {this.testRenderer(clusters, stats)} // trying to edit the icons here
}
});
});
}
private testRenderer(clusters, stats) {
const svg = // create svg here
return new google.maps.Marker({
// position is required here but I don't have that value
icon: {
url: `data:image/svg+xml;base64,${svg}`,
scaledSize: new google.maps.Size(45, 45),
},
label: {
text: String(stats.count),
color: "rgba(255,255,255,0.9)",
fontSize: "12px",
},
});
}
const renderer = {
render({ count, position }) {
return new google.maps.Marker({
label: { text: String(count), color: "white", fontSize: "12px" },
position,
icon: "url to the file",
// adjust zIndex to be above other markers
zIndex: Number(google.maps.Marker.MAX_ZINDEX) + count,
})
}
}
// pass your function to the markerCluster constructor
const cluster = new MarkerClusterer({map, markers, renderer})
Missing a return statement on the render method. High fives all round.

How to use Mapbox Geocoding API in JavaScript to reverse geocode a point?

I have read a lot of SO questions and answers and looked at the Mapbox documentation but I can't see a simple example of how to reverse geocode from a latitude/longitude point. The example only shows the URL to use to make a query but presumably there's a way to do it using the MapboxGeocoder object?
So, here, what would I use to display the reverse geocode for lat/long when the user clicks on the map?
var map = new mapboxgl.Map({
container: document.getElementsByClassName("js-map")[0],
center: [0, 51],
style: "mapbox://styles/mapbox/streets-v11",
zoom: 11
});
map.on("click", function(ev) {
// 1. Reverse geocode `ev.lngLat.lat` and `ev.lngLat.lng`
// 2. Do something with the result.
});
You could use the Mapbox SDK for JS which is a wrapper around the rest API.
<script src="https://unpkg.com/#mapbox/mapbox-sdk/umd/mapbox-sdk.min.js"></script>
var mapboxClient = mapboxSdk({ accessToken: mapboxgl.accessToken });
mapboxClient.geocoding
.reverseGeocode({
query: "lng,lat"
})
.send()
.then(function(response) {
if (
response &&
response.body &&
response.body.features &&
response.body.features.length
) {
var feature = response.body.features[0];
}
});
The MapboxGeocoder plugin for Mapbox GL JS is to provide an out of the box search box interface, not for calling the API directly.
Unless someone comes up with a better answer, here's what I've done.
To do this you don't need to include any of the Mapbox Geocoding CSS/JS, which I assume are only needed for displaying the widget that a user types into, and which can display a list of results:
https://api.mapbox.com/mapbox-gl-js/plugins/mapbox-gl-geocoder/v4.4.2/mapbox-gl-geocoder.css
https://api.mapbox.com/mapbox-gl-js/plugins/mapbox-gl-geocoder/v4.4.2/mapbox-gl-geocoder.min.js
https://cdn.jsdelivr.net/npm/es6-promise#4/dist/es6-promise.min.js
https://cdn.jsdelivr.net/npm/es6-promise#4/dist/es6-promise.auto.min.js
So, assuming we have a <div class="js-map"></div>, and have included the mapbox-gl-js CSS and JS files, here we use jQuery to do the geocoding request:
var api_key = "YOUR-API-KEY";
var map = new mapboxgl.Map({
container: document.getElementsByClassName("js-map")[0],
center: [0, 51],
style: "mapbox://styles/mapbox/streets-v11",
zoom: 11
});
map.on("click", function(ev) {
$.get(
"https://api.mapbox.com/geocoding/v5/mapbox.places/" +
ev.lngLat.lon + "," + ev.lngLat.lat + ".json?access_token=" + api_key,
function(data) {
console.log(data);
}
).fail(function(jqXHR, textStatus, errorThrown) {
alert("There was an error while geocoding: " + errorThrown);
});
});

Embedding Earth Engine code into my AngularJS application and applying NDVI layer on a google map

Has anyone been able to use Earth Engine API inside their front-end JavaScript Code. I have been trying to follow the demo on the earth-engine repo to apply a layer on a map but with no results. I don't know exactly what's wrong but the function ee.data.authenticate doesn't seem to fire though I have my client ID passed to it.
You'll need to authenticate using the client-side OAuth method described here: https://developers.google.com/earth-engine/npm_install. Beyond that, you can use Google Maps and Earth Engine APIs as usual:
HTML:
<div id="map" style="width: 600px; height: 400px"></div>
JS:
// Load client library.
const ee = window.ee = require('#google/earthengine');
const CLIENT_ID = 'YOUR_CLIENT_ID';
window.initMap = function () {
// Initialize client library.
const initialize = function () {
ee.initialize(null, null, () => {
createMap();
}, (e) => {
console.error('Initialization error: ' + e);
});
};
// Authenticate using an OAuth pop-up.
ee.data.authenticateViaOauth(CLIENT_ID, initialize, (e) => {
console.error('Authentication error: ' + e);
}, null, () => {
ee.data.authenticateViaPopup(initialize);
});
};
function createMap() {
// Initialize map.
const mapEl = document.querySelector('#map');
const map = new google.maps.Map(mapEl, {
center: new google.maps.LatLng(39.8282, -98.5795),
zoom: 5
});
// Load EE image.
const image = ee.Image('srtm90_v4');
image.getMap({ min: 0, max: 1000 }, ({ mapid, token }) => {
// Create Google Maps overlay.
const mapType = new google.maps.ImageMapType({
getTileUrl: ({ x, y }, z) =>
`https://earthengine.googleapis.com/map/${mapid}/${z}/${x}/${y}?token=${token}`,
tileSize: new google.maps.Size(256, 256)
});
// Add the EE layer to the map.
map.overlayMapTypes.push(mapType);
});
}
In a real application you should also show a "Log in" button and not open the popup until then — otherwise the browser's popup blocking may prevent it from appearing.

Google places API "rankby=location" problems

So I am currently working on a prototype that utilises the Google places API, and am struggling with the API documentation shown here: https://developers.google.com/places/web-service/search
In short, the rankby='location' option now seems to require a radius setting, contrary to the documentation (if radius is commented out, the API request errors out: "Uncaught Error: Missing parameter. You must specify radius.").
Infact, the rankby='location' option does not seem to do anything, as the list that I get back has items further away (using lat/long dist conversion), listed before items closer etc - and is the exact same response as when I comment that option out (searching for all parks within radius).
codepen:
https://codepen.io/creative-lab-sydney/pen/18afe9d8500d490cbe93be68109c5b82
code sample for simple places api "nearbySearch" request:
const places = document.querySelector('#places');
const RADIUS = 700;
let request = {
location: userLocation, // taken from navigator.geoLocation
radius: RADIUS, // mandatory now (?) RADIUS = 50;
type: ['park'],
rankby: 'distance',
};
const service = new google.maps.places.PlacesService(document.querySelector('#moo'));
service.nearbySearch(request, callback);
function callback(results, status) {
if (status === google.maps.places.PlacesServiceStatus.OK) {
Object.keys(results).map(key => {
let item = results[key];
let placeLocation = {
lat: item.geometry.location.lat(),
lng: item.geometry.location.lng()
};
let distObj = getDistance(userLocation, placeLocation);
let div = document.createElement('div');
div.appendChild(document.createTextNode(`${item.name} - dist: ${distObj.text}`));
places.appendChild(div);
});
}
}
Has anyone else struggled with this API recently?
OK, so it seems I had the syntax slightly wrong.
The syntax for the request should be:
var request = {
location: userLocation,
type: ['park'],
rankBy: google.maps.places.RankBy.DISTANCE,
};

how to validate bing maps api key?

I am using the leaflet bing maps plugin.
How can I validate the bing maps api key before it's used by leaflet?
If I allow an invalid key to be used like this:
var bing_key = "funTimeWithBingMaps"
baseMapUrl = new L.BingLayer(bing_key)
Then bing maps reports:
"Leaflet Bing Plugin Error - Got metadata: Access was denied. You may have entered your credentials incorrectly, or you might not have access to the requested resource or operation."
And then map.removeLayer(baseMapUrl); fails to remove the layer.
You need to create your own key by using this website: http://www.bingmapsportal.com/
Once you have logged in with a Microsoft account (aka Live ID), you will be able to generate several keys based on your usage. For more information about the type of key, check the MSDN here: http://msdn.microsoft.com/en-us/library/ff428642.aspx
It's really ugly but here's a 'solution'.
var map, osm, bing, count_down = 50;
function first_part()
{
map = new L.Map('map', {center: new L.LatLng(67.6755, 33.936), zoom: 10 });
osm = new L.TileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png');
bing = new L.BingLayer("MyBingApiKeyGoesHere");
setTimeout(function () { second_part(bing); }, 100);
}
function second_part(binz)
{
if (typeof(binz.meta.statusCode) == 'undefined') {
count_down--;
if (count_down == 0) {
alert("abandon operation");
return;
}
setTimeout(function () { second_part(binz); }, 100);
return;
}
if (binz.meta.statusCode == 200) {
alert("OK");
map.addLayer(bing);
map.addControl(new L.Control.Layers({'OSM':osm, "Bing":bing}, {}));
}
else {
alert("WRONG: count_down: "+count_down+" statusCode: "+binz.meta.statusCode);
}
}
first_part();

Categories