Geometry has too many edges (Google Earth Engine). Help me - google-earth-engine

I have problem during working in Google Earth Engine. I was processing some vector files. And i am getting below code:

The geometry has too many vertices. You can try to simplify it using:
// Get a feature collection and subset the first feature.
var feature = ee.FeatureCollection("TIGER/2018/States").first();
// Simplify the feature - think of max error as resolution.
// Setting to 100 means that the geometry is accurate to
// within 100 meters, for example.
var featureSimple = ee.Feature(feature).simplify({maxError: 100});
or for a ee.FeatureCollection:
// Get a feature collection.
var featureCol = ee.FeatureCollection("TIGER/2018/States");
// Simplify each feature in the collection, by mapping the
// .simplify() function over it.
var simplifiedCol = featureCol.map(function(feature) {
return feature.simplify({maxError: 100});
});

Related

Is there a way of finding cities within a route with Google Maps API?

Is there a way of obtaining the cities a route traced by DirectionsService.route() goes through?
For example, in the route in https://goo.gl/maps/trHkPUNzuDFEjYT27 , roads belonging to the cities of Sao Paulo (starting point), Anhanguera, Cajamar, Jundiai, (others...) and Campinas (ending point).
If we input the starting and ending point in the DirectionsService.route() method, we obtain a list of legs, which includes the road, mileage, and time to travel, but not the cities they belong to.
Is there a way of obtaining this data without calling additional API's ? Cost is an important issue when considering Maps API.
EDIT: Clarified that solution should not involve additional calls. This solution is not much better than calling PlacesService for each leg of the route, since it merely boxes parts of the route, and calls them anyways.
My suggestions would be simply to abandon the approach of using the Google API for everything. Undoubtedly, it's the best navigation tool, and it's for this reason it's that expensive. So, I'd suggest to use some other method for the geocoding that's not through Google, especially if you're only looking for big cities (as is appears in your example). There's some 'free' APIs that already exist (in truth, they're usually never really free) - I'd only suggest this if you're serverless. In that case, I'd go with Nominatim - it has no limit caps (kind of, see operations.osmfoundation.org/policies/nominatim - you can spam it, but it's discouraged), no API keys, and is completely free - the only issue, of course, is that as you mentioned you'd have to go through each point and make a request to an API, which would take a lot of time. However, I'd do:
let zoom = 12; // Adjust to your needs: see nominatim.org/release-docs/develop/api/Reverse. Higher numbers will result in more places being matched, while lower numbers will result in faster execution.
let coords = [];
const stepList = [];
Array.from(googleResponse.routes[0].legs).forEach(leg => {
stepList.push(...leg.steps);
});
stepList.forEach(e => {
coords.push([e.endLocation.lat, e.endLocation.long]);
});
coords.push([legList[0].startLocation.lat, legList[0].startLocation.long]);
let arr = [];
let promises = [];
let bboxes = [];
const loopOn = (i, cb) => {
const coord = coords[i];
const nextLoop = () => {
i+1 < coords.length? loopOn(i+1, cb) : cb();
}
let makeRequest = true;
for (let bbox of bboxes) {
if (coord[0] >= bbox[0]
&& coord[0] <= bbox[1]
&& coord[1] >= bbox[2]
&& coord[1] <= bbox[3]){ // If it is within a bounding box we've already seen
makeRequest = false; // there's no need to geocode it, since we already roughly know it's in an area we have already saved.
break;
}
}
if (makeRequest){
var request = $.ajax({
url: `https://nominatim.openstreetmap.org/reverse?format=jsonv2&lat=${coord[0]}&lon=${coord[1]}&zoom=${zoom}`,
type:'GET',
dataType: 'jsonp',
success: resp => {
thisPlace = resp.address.city || resp.address.town || resp.address.village;
thisPlace && arr.indexOf(thisPlace) === -1 && arr.push(thisPlace);
bboxes.push(resp.boundingbox);
nextLoop();
}
});
} else {
nextLoop();
}
};
loopOn(0, () => {
/*The rest of your code*/
});
This code simply goes through each leg (where I'm assuming googleResponse is the unfiltered but JSONified response from the Directions API, and requests it from Nominatim. I've made it a tad bit more efficient using Nominatim's bounding boxes, which return the rectangle around each city/village area, so we don't need to make a request if an instruction/step is literally simply to turn a corner in the same square/suburb/city district/city (this can be defined using the zoom variable).
The problem with this is that Nominatim, being free and quite unoptimised, is obviously not the fastest API out there. Even if Google's servers ran on a slow connection, they'd still be faster simply because they've optimised their product to run faster, using some low-level code. Meanwhile, Nominatim simply does a basic lookup from a file (no rainbow hashing, etc.), so it has to manually narrow down the area.
The solution would be to use a custom dataset. Obviously, this would require a backend to store it on, since downloading the entire CSV to the frontend with each load would take literal hours (and on every reload!). All you'd really need to do for this is replace the AJAX API request with a call to the csv-parser module (or any other parsing function), which works in much the same way regarding promises/async, so you could literally just replace the code with the example on their website:
let resp = [];
fs.createReadStream(<your file here.csv>)
.pipe(csv())
.on('data', (data) => resp.push(data))
.on('end', () => {
results = search(resp, coords);
thisPlace = results.address.city || results.address.town || results.address.village;
thisPlace && arr.indexOf(thisPlace) === -1 && arr.push(thisPlace);
nextLoop();
});
Also, you could remove the bounding-box code, since you don't need to save request time anymore.
However, rearranging it like so would be faster:
let resp = [];
fs.createReadStream(<your file here.csv>)
.pipe(csv())
.on('data', (data) => resp.push(data))
.on('end', () => {
let coords = [];
const stepList = [];
Array.from(googleResponse.routes[0].legs).forEach(leg => {
stepList.push(...leg.steps);
});
stepList.forEach(e => {
coords.push([e.endLocation.lat, e.endLocation.lng]);
});
coords.push([legList[0].startLocation.lat, legList[0].startLocation.lng]);
let arr = [];
let promises = [];
let bboxes = [];
coords.forEach(coord => {
let results = search(coords);
let thisPlace = results.address.city || results.address.town || results.address.village;
thisPlace && arr.indexOf(thisPlace) === -1 && arr.push(thisPlace);
};
/*The rest of your code*/
});
The next thing we need is the actual search function, which is the complicated bit. We need to find something that's quick, but also mostly correct. The actual implementation depends on the format of your file, but here's a quick rundown of what I'd do:
Create two duplicates of resp, but sort one (we'll call this array a_o) by longitude and the other one by latitude (a_a). Ensure you don't use var or let or const when defining these arrays, just... define them.
For each one, remove anything not within a 25km (radius) of the point on the longitude axis in a_o, and the same but with latitude for a_a
delete both arrays to clear the space they are taking up in the RAM.
Find any item that's in both arrays, and put these in an array called a_c
Filter any items which are within 3-4km of each other (make sure to keep one of the points, though, not delete both!)
Go through each item, and work out the absolute distance to the point (using this algorithm - remember, the earth is a sphere, basic Pythagorean thereom will not work!
If you find any item with a distance less than 20km, and has a city or village or town attached, break and return the name.
If you finish, i.e never break, return undefined.
Other then that, you can go with mostly any CSV which contains:
The city's name
The central latitude
The central longitude
I hope this helps. In conclusion, go with the first, tried-and-tested, premade method if you're only doing 5-6 simple routes an hour. If you've got gajillions of waypoints, download the data and spend half an hour or so making what is essentially your own geocoding system. It'll well be worth the performance bump.

Google Maps API In Apps Script Keeps Failing

I'm using google apps script to code a distance finder for Google Maps. I've found examples of such, but they keep failing, so I thought I'd code my own. Sadly, this is failing with the same error:
TypeError: Cannot read property "legs" from undefined. (line 16).
It seems to be that it's sometimes working, and sometimes not. I have a few (3) places in my sheet that are calling the same functions, and at times one or more will return a valid response.
I saw elsewhere that people were suggesting using an API key to make sure that you get a good response, so that's what I've implemented below. (api keys redacted! is there a good way to tell if they've been recognised?)
Any ideas what might be going awry?!
Thanks in advance,
Mike
function mikeDistance(start, end){
start = "CV4 8DJ";
end = "cv4 9FE";
var maps = Maps;
maps.setAuthentication("#####", "#####");
var dirFind = maps.newDirectionFinder();
dirFind.setOrigin(start);
dirFind.setDestination(end);
var directions = dirFind.getDirections();
var rawDistance = directions["routes"][0]["legs"][0]["distance"]["value"];
var distance = rawDistance/1609.34;
return distance;
}
Here's my short term solution while the issue is being fixed.
Not ideal, but at least reduces using your API limit as much as possible.
function getDistance(start, end) {
return hackyHack(start, end, 0);
}
function hackyHack(start, end, level) {
if (level > 5) {
return "Error :(";
}
var directions = Maps.newDirectionFinder()
.setOrigin(start)
.setDestination(end)
.setMode(Maps.DirectionFinder.Mode.DRIVING)
.getDirections();
var route = directions.routes[0];
if (!route) return hackyHack(start, end, level+1); // Hacky McHackHack
var distance = route.legs[0].distance.text;
// var time = route.legs[0].duration.text;
return distance;
}

Google maps API, Wordpress plugin, Trying to get Imperial output

I have downloaded a google maps plugin for wordpress, which outputs in km, but I'm in the UK so I need miles.
I have added the line
unitSystem: google.maps.DirectionsUnitSystem.IMPERIAL
to the code
function calcRoute() {
var start = document.getElementById('origin').value;
var end = document.getElementById('destination').value;
var request = {
origin:start,
destination:end,
travelMode: google.maps.DirectionsTravelMode.DRIVING,
unitSystem: google.maps.DirectionsUnitSystem.IMPERIAL
};
but it still returns km.
Any ideas how I get it to return miles?
We don't know what value you are checking but if we are talking about the Distance object, the documentation says the value property is always returned in meters, while the text property is a representation of the value using the UnitSystemspecified.
https://developers.google.com/maps/documentation/javascript/reference#Distance
Otherwise, you always can convert from METRIC to IMPERIAL using your own maths.

'item.geometry.location.kb' and 'item.geometry.location.jb' returning undefined

I'm using Google Maps to get an autocomplete list of cities.
I used to use item.geometry.location.kb as the longitude and item.geometry.location.jb as the latitude, but they are not defined since today/yesterday.
Apparently, one has to use item.geometry.location.lng() and .lat() instead.
I didn't know that and I have an app using item.geometry.location.kb and jb in Google Play and the AppĀ Store.
So my apps are not working any more.
Why has a change has been made and how can I revert to kb and jb?
autocomplete = new google.maps.places.Autocomplete(input, options);
google.maps.event.addListener(autocomplete, 'place_changed', function(event) {
var item = autocomplete.getPlace();
curLon = item.geometry.location.kb;
curLat = item.geometry.location.jb;
// ...
Don't use undocumented properties of the Google APIs. They can and do change with every release.
geometry.location is a google.maps.LatLng object, and the documented methods to get latitude and longitude are .lat() and .lng().

LatLng from Google Maps Polygon getPath()

Based on the Google Maps JavaScript API v3 documentation, google.maps.Polygon class's getPath() function returns an MVCArray. In a straightforward case, a Polygon's path can be a single array of LatLngs that are converted to the MVCArray type upon being passed into the google.maps.Polygon class's setPath() function.
The above case is what I'm dealing with currently. I pass in an array of LatLngs, and return (what I assume is) an MVCObject when I call getPath() on my Polygon object. My question is: How do I convert this MVCObject back into a single array of LatLngs that form the Polygon's shape? Is there some built in Google Maps API v3 way that I'm missing? I feel like there has to be some sort of obvious built in conversion function or something in the API that's eluding me.
Any help would be appreciated.
When you call Polygon.getPath()api-doc, the return is an MVCArrayapi-doc of LatLng instances that represent the first path of the Polygon. You can directly get to the members of the MVCAarray in two ways:
Call MVCAarray.getArray, which will return the underlying JavaScript Array that contains LatLng members.
Use MVCArray.getAt( index ), which will return whatever is at that index in the MVCArray (a LatLng in this case). This provides you a way to setup a JavaScript for loop to iterate over the members of the array.
You can also indirectly work with the members of the MVCArray by using the forEach(callback:function(*, number)) function. In this case, you must pass a callback function that accepts two parameters:
The actual member element of the MVCArray.
The array index where that element is located.
var polygonBounds = polygon.getPath();
var bounds = [];
for (var i = 0; i < polygonBounds.length; i++) {
var point = {
lat: polygonBounds.getAt(i).lat(),
lng: polygonBounds.getAt(i).lng()
};
bounds.push(point);
}

Resources