I am developing a business directory site whose search will be driven by Google Maps. Users will be able to search for businesses in their area based on various criteria, but mainly the idea is that if you search for e.g. "plumbers in New Jersey", you'll get a result of all plumbers in New Jersey. However, if you search for "plumbers in Jersey Shore", you should get only the plumbers that operate in Jersey Shore, which for this example would be a suburb or other type of sub-division of greater New Jersey. As an aside, I'm stripping out "plumbers in", and only passing the actual geographic search term, so my actual google maps search is only "New Jersey" or "Jersey Shore". So don't focus on the actual search text itself.
This is my search snippet:
var latlng = results[0].geometry.location;//results is google maps search result for address
console.log(results[0]);
map.setCenter(latlng.lat(), latlng.lng());
map.addMarker({
lat: latlng.lat(),
lng: latlng.lng(),
icon: 'https://maps.google.com/mapfiles/kml/shapes/schools_maps.png'
});
var closestmarkers = [];
var MAXDISTANCE = 5;//radius of our search area in km
closestmarkers = find_n_closest_markers(latlng, n, MAXDISTANCE);//see below snippet for function definition
This is the javascript that figures out which markers are closest to the searched :
function find_n_closest_markers(latlng, n, MAXDISTANCE){
var lat = latlng.lat();
var lng = latlng.lng();
var R = 6371; // radius of earth in km
var distances = [];
var closest = -1;
for (i = 0; i < map.markers.length; i++) {
var mlat = map.markers[i].position.lat();
var mlng = map.markers[i].position.lng();
var dLat = rad(mlat - lat);
var dLong = rad(mlng - lng);
var a = Math.sin(dLat / 2)
* Math.sin(dLat / 2)
+ Math.cos(rad(lat))
* Math.cos(rad(lat))
* Math.sin(dLong / 2)
* Math.sin(dLong / 2);
var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
var d = R * c;
distances[i] = { distance: d, index: i };
}
distances.sort(comparedistances);
//remove all distances greater than MAXDISTANCE
toofarindex = -1;
for (j = 0; j < distances.length; j++)
{
if(distances[j].distance >= MAXDISTANCE)
{
toofarindex = j;
break;
}
}
if (toofarindex != -1)
{
distances = distances.splice(0, toofarindex - 1);
}
//return first n + 1 items(the closest marker is the one indicating the user's current/preferred location. which doesn't count)
if (distances.length > n + 1)
{
distances = distances.splice(0, n + 1);
}
return distances;
}
I am not concerned about the actual function. That works 100%. What I'm looking for is how to figure out, based on the search term, what the value of MAXDISTANCE should be. 5 is just a good compromise constant for now, but I need to know that New Jersey is e.g. 20 miles in diameter, whereas Jersey Shore is only 5 miles (those figures come straight out of my ear, not an actual map).
The geocoder also returns a viewport and a bounds for the result. If you need a diameter, you can convert one of those to a distance (the width or the height of the bounds will give you a diameter, if not use that bounds to bound your search.
Related
I want to cross compare multiple objects containing location data (latitude, longitude) for the distance between them. The goal is to calculate the farthest two locations from a bunch of locations. I already know how to calculate the distance between two locations but what to do if you have multiple locations?
function FarthestDistance(points) {
// here this function should cross compare all objects within 'points' to calculate which two locations have the furthest distance between them
// the function should calculate the distance between each point in a recursive manner and return the two farthest points back
}
var obj = {{lat1,lon1},{lat2,lon2},{lat3,lon3},{lat4,lon4}};
FarthestDistance(obj);
Hope it is clear now. Thanks.
Alright, so this is for a Magic Mirror module I am making. Its a wrapper for the Traccar Service to be deployed on magic mirror. I needed to calculate the farthest two points on the map from an object containing multiple locations of each registered user. Then once I have the farthest two points on the map, I could calculate the middle point between them to set it as center of the map. Now I am working on the zooming of the map to include all the markers on the map.. anyways, for the problem I explained here the solution was found after little bit of research. Here it goes.
function toRadians (deg){ // Helper function
return deg * (Math.PI/180);
}
function toDegrees (rad){ // Helper function
return rad * (180/Math.PI);
}
function distance (obj){ // Helper function from https://www.movable-type.co.uk/scripts/latlong.html | http://mathforum.org/library/drmath/view/51822.html
var R = 6371e3; // metres
var φ1 = obj[0][0];
var φ2 = obj[1][0];
var Δφ = obj[1][0]-obj[0][0];
var Δλ = obj[1][1]-obj[0][1];
var a = Math.sin(Δφ/2) * Math.sin(Δφ/2) +
Math.cos(φ1) * Math.cos(φ2) *
Math.sin(Δλ/2) * Math.sin(Δλ/2);
var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
return (R * c);
}
function midPoints(obj){ // Helper functions (which I modified for the specifications) from https://stackoverflow.com/questions/477591/algorithm-to-find-two-points-furthest-away-from-each-other | https://www.movable-type.co.uk/scripts/latlong.html | http://mathforum.org/library/drmath/view/51822.html
var self = this;
var solution = {"start": [], "end": [], "distance": 0};
for (i = 0; i < obj.length; i++) {
for (j = i+1; j < obj.length; j++) {
var newpoint = [
[
self.toRadians(obj[i][0]),
self.toRadians(obj[i][1])
],
[
self.toRadians(obj[j][0]),
self.toRadians(obj[j][1])
]
];
var distance = self.distance(newpoint);
if (distance > solution.distance){
solution = {"start": [obj[i][0],obj[i][1]], "end": [obj[j][0],obj[j][1]], "distance": distance}
}
}
}
var Bx = Math.cos(solution.end[0]) * Math.cos(solution.end[1]-solution.start[1]);
var By = Math.cos(solution.end[0]) * Math.sin(solution.end[1]-solution.start[1]);
var latMid = Math.atan2(Math.sin(solution.start[0]) + Math.sin(solution.end[0]),Math.sqrt((Math.cos(solution.start[0])+Bx)*(Math.cos(solution.start[0])+Bx) + By*By ) );
var lonMid = solution.start[1] + Math.atan2(By, Math.cos(solution.start[0]) + Bx);
return {"lat": self.toDegrees(latMid), "lon": self.toDegrees(lonMid), "distance": solution.distance};
}
I have an array where every array value is an object { lat: ___, lng: ___ }. I'd like to input a fraction (let's say 20% (0.2)) and get the specific coordinate for that fraction, based on the distance from point 0. What this means that given the distance d (sum of all individual paths between points), the coordinate should be 1/5 of d. If I were to set the fraction to 0 this would give me the first coordinates in the path. And if I were to set the fraction to 1 it'd give me the last.
I've read up on coordinates based on fractions here on SO (Calculate point between two coordinates based on a percentage) and used this function in my algorithm.
What I figured I might do is add all paths between each point together to get a sum d. Then for every distance between points f I'd check to see if this distance were lower than d. If true, deduct f from d. If false, use the function from the link above and input the remaining fraction d. I thought this'd give me the correct coordinate.
This works to some extent but for individuals paths with a long distance it leaves gaps throughout the full path.
Here's the code for the algorithm as well as the code for generating this picture. I think the problem occurs in modules.fractionCoordinateArray.
var modules = {};
modules.distance = function(lat1, lon1, lat2, lon2, km = true) {
var p = 0.017453292519943295,
c = Math.cos;
var a = 0.5 - c((lat2 - lat1) * p) / 2 + c(lat1 * p) * c(lat2 * p) * (1 - c((lon2 - lon1) * p)) / 2;
if (km) {
return 12742 * Math.asin(Math.sqrt(a)); // 2 * R; R = 6371 km
} else {
return 12742 * Math.asin(Math.sqrt(a)) * 1000; // meters
}
};
modules.distanceSum = function(arr, km = true) {
var total = 0;
for (var i = 0; i < arr.length; i++) {
if (i > 0) {
total += modules.distance(arr[i - 1].lat, arr[i - 1].lng, arr[i].lat, arr[i].lng, km);
}
}
return total;
};
modules.fractionCoordinateArray = function(arr, frac = 0) {
var dist = modules.distanceSum(arr),
fractions = [];
if (frac <= 0) {
return {
lat: arr[0].lat,
lng: arr[0].lng
};
} else if (frac >= 1) {
return {
lat: arr[arr.length - 1].lat,
lng: arr[arr.length - 1].lng
};
}
for (var i = 0; i < arr.length; i++) {
if (i > 0) {
var frc = modules.distance(arr[i - 1].lat, arr[i - 1].lng, arr[i].lat, arr[i].lng) / dist;
if (frac > frc) {
frac -= frc;
} else {
return modules.fractionCoordinate(arr[i - 1].lat, arr[i - 1].lng, arr[i].lat, arr[i].lng, frac);
}
}
}
};
modules.fractionCoordinate = function(lat1, lng1, lat2, lng2, per) {
return {
lat: lat1 + (lat2 - lat1) * per,
lng: lng1 + (lng2 - lng1) * per
};
};
/*
For generating the image I used this;
*/
var dst = [],
path = [{
lat: 12,
lng: 12
}, {
lat: 13.75,
lng: 13
}, {
lat: 14,
lng: 17
}, {
lat: 59,
lng: 18
}]; // Example path
for (var i = 0; i < 51; i++) {
var crd = modules.fractionCoordinateArray(path, i / 50);
dst.push('markers=size:tiny%7C' + crd.lat + ',' + crd.lng);
}
console.log('https://maps.googleapis.com/maps/api/staticmap?autoscale=2&size=600x300&maptype=roadmap&format=png&visual_refresh=true&' + dst.join('&'));
I'm looking for help on how to solve this problem in the most efficient way. Thank you!
The logic in fractionCoordinateArray() would be more or less correct if you first converted the fraction frac into an absolute distance totalDistRemaining (by multiplying it by the total path distance), and then worked with (i.e., subtracted individual edge lengths off) that from that point on.
Then when you call
return modules.fractionCoordinate(arr[i-1].lat, arr[i-1].lng, arr[i].lat, arr[i].lng, frac);
you should instead pass
totalDistRemaining / distance(arr[i-1].lat, arr[i-1].lng, arr[i].lat, arr[i].lng)
as the last parameter.
Finally, to improve speed, you could add a third entry to each array element that records the total distance along the path so far (so this will be 0 at the start, and equal to the total path length at the end). This values will be nondecreasing, so you can now find the path segment containing any given path distance in O(log n) time with binary search.
I'm trying to get my REST API to only send me back items that are within a certain radius. I've written code to calculate the distance between two points and only send me the ones within the desired radius. However, as of now I'm using an arbitrary latitude and longitude as the point of comparison. How can I implement using my own location into this algorithm?
app.get('/posts', function(req, res) {
// Get 'posts' collection from db
db.collection('posts', function(err, collection) {
// Get all documents in the 'posts' collection
collection.find().toArray(function(err, items) {
// Documents to send back
var results = []
//Function that calculates distance between two points.
function calculateDistance (lat1, lat2, lon1, lon2) {
var dLat = deg2rad(lat2-lat1);
var dLon = deg2rad(lon2-lon1);
var a =
(Math.sin(dLat/2) * Math.sin(dLat/2) +
Math.cos(deg2rad(lat1)) * Math.cos(deg2rad(lat2)) *
Math.sin(dLon/2) * Math.sin(dLon/2))
;
var c = (2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)));
var d = (6371 * c); // Distance in km
var e = (d/1.6) //Convert distance in km to miles.
return e
}
// Iterate over each document and check if the distance is correct
for (var i = 0; i < items.length; i++) {
var item = items[i]
if ((calculateDistance(item.latitude, 43.7035798, item.longitude, -72.2887838) > 25)) {
results.push(item)
}
}
res.send(results);
});
});
});
navigator.geolocation is what you want.
https://developer.mozilla.org/en-US/docs/Web/API/Geolocation/Using_geolocation
In Firefox at least, you should get a popup that asks if you want to allow your browser to send your location. You've probably have gotten them for websites that use the Google Maps API.
Say I have an array in this format:
var arr = [{lat: 123.123, lng: 321.321}, {lat: 567.567, lng: 765.765}]
Based on some map coordinates, how can I most effectively find the object with coordinates closest to the map coordinates?
A naive solution is to do:
var getClosestPoint = function(coord, coordArray) {
var bestDistance = null;
var bestCoord = null;
for (var i = 0; i < coordArray.length; ++i) {
var currentCoord = coordArray[i];
var distance = getDistance(coord, currentCoord);
if ((bestDistance == null) || (distance < bestDistance)) {
bestDistance = distance;
bestCoord = currentCoord;
}
}
return {'distance': bestDistance, 'coord':bestCoord};
};
// Based on the solution here:
// http://stackoverflow.com/questions/365826/calculate-distance-between-2-gps-coordinates
var getDistance = function(coordA, coordB) {
var R = 6371; // km
var dLat = (coordB.lat-coordA.lat).toRad();
var dLon = (coordB.lng-coordA.lng).toRad();
var lat1 = coordA.lat.toRad();
var lat2 = coordB.lat.toRad();
var a = Math.sin(dLat/2) * Math.sin(dLat/2) +
Math.sin(dLon/2) * Math.sin(dLon/2) * Math.cos(lat1) * Math.cos(lat2);
var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
var d = R * c;
return d;
};
In other words, the naive solution is to just iterate through all the points, updating the current best distance and the corresponding coordinate. If your list of points is small, this may be reasonable to do. However, a more effective solution is to use a tree structure, where each internal node in the tree is represented by the mean coordinate of all points under that node. You then search the tree by descending the node with the closest mean coordinate until you reach a leaf. This approach allows you to throw out a larger number of candidate points in each iteration, giving a logarithmic solution.
In other words, a more effective solution looks like:
var getClosestPoint = function(coord, coordNode) {
var children = coordNode.getChildren();
if (children.length == 0) {
return coordNode.getCenterCoord();
}
var closestChild = null;
var bestDistance = 0.0;
for (var i = 0; i < children.length; ++i) {
var currentCoord = children[i].getCenterCoord();
var distance = getDistance(coord, currentCoord);
if ((closestChild == null) || (distance < bestDistance)) {
closestChild = children[i];
bestDistance = distance;
}
}
return getClosestPoint(coord, closestChild);
}
Of course, this assumes that you've built up such a tree in the first place. If you are running "getClosestPoint()" repeatedly with the same set of points, then it is probably worthwhile to build up such a structure (if you only execute "getClosestPoint()" once for any given set of points, then the naive solution may be reasonable). The articles on K-D trees and quad trees may be of interest for further reading on this general approach and how to build up and partition the points into these trees.
I believe this should work on a square grid. If the values reset after a certain point, like on earth, there needs to be some adjustment to this solution.
function calculateDistance(x1, x2, y1, y2){
return Math.sqrt(Math.pow((x1 - x2), 2) + Math.pow((y1 - y2), 2));
}
var retrievedCoords = {lat: 12234, lng: 135};
var closestPoint = arr[0];
var distanceToClosestPoint = calculateDistance(retrievedCoords.lat, arr[0].lat, retrievedCoords.lng, arr[0].lng);
for (var i = 1; i < arr.length; i++){
var tempDist = calculateDistance(retrievedCoords.lat, arr[i].lat, retrievedCoords.lng, arr[i].lng);
if (tempDist > distanceToClosestPoint){
closestPoint = arr[i];
distanceToClosestPoint = tempDist;
}
}
I would like to create a dot density map using Google Maps. I have all the counties of my state outlined, along with their corresponding populations. I want to know how I could place a number of dots randomly within each county to represent the population of that county. We want to make a dot density map instead of a choropleth map because we like the representation better, but I can't figure out how to distribute dots among a polygon outline.
This is a poor example of sort of what I'm looking to make.
Well, I've come to a very inefficient yet suitable solution to my problem. In case anybody would either like to help me improve my method or use it themselves, this is what I did.
I used this answer as a guide to test whether or not a point would fall in a particular polygon. As I create the polygons that outline the border of counties in my state, I add each latitude to one array, and each longitude to another. I then determine min and max values for each array as a bounding box that a point would have to be in in order to fall within the county lines. I then pick random numbers between those mins and maxes and test whether they fall within the county. If they do, I add a marker there. I do those within a loop that counts how many markers are added until it is proportional to the population of that particular county. Here is the code:
function addMarkers() {
var loc = "Resources/CaliforniaCounties.json";
$.getJSON(loc, function (data) {
$.each(data.features, function (key, val) {
var xArray = []; //
var yArray = []; //
var coords = [];
var latlng;
var bounds = new google.maps.LatLngBounds();
var polygon;
$.each(val.geometry.coordinates[0], function (i, item) {
latlng = new google.maps.LatLng(item[1], item[0]);
xArray.push(item[0]); //
yArray.push(item[1]); //
coords.push(latlng);
bounds.extend(latlng);
});
var nverts = xArray.length; //
var maxX = Math.max.apply(null, xArray); //
var maxY = Math.max.apply(null, yArray); //
var minX = Math.min.apply(null, xArray); //
var minY = Math.min.apply(null, yArray); //
polygon = new google.maps.Polygon({
paths: coords,
strokeColor: "#000000",
strokeOpacity: 1,
strokeWeight: 01,
fillColor: "#cccccc",
fillOpacity: .5
});
polygon.center = bounds.getCenter();
addPolygonClickListener(polygon, val);
polygon.setMap(map);
polygonArray[val.properties.Name] = polygon;
var i = 1;
while( i < populations[val.properties.Name] / 10000){
var testX = Math.random() * (maxX - minX) + minX; //
var testY = Math.random() * (maxY - minY) + minY; //
if(pnpoly(nverts, xArray, yArray, testX, testY) == 1){ //
var mlatlng = new google.maps.LatLng(testY, testX); //
var marker = new google.maps.Marker({ position: mlatlng, icon: "Resources/dot.png", map: map }); //
i++;
}
}
});
});
function pnpoly(nvert, vertx, verty, testx, testy)
{
var i, j, c = 0;
for (i = 0, j = nvert-1; i < nvert; j = i++)
{
if ( ((verty[i]>testy) != (verty[j]>testy)) &&
(testx < (vertx[j]-vertx[i]) * (testy-verty[i]) / (verty[j]-verty[i]) + vertx[i]) )
{
c = !c;
}
}
return c;
}
I have a more efficient way to do this. You can use the same features to create color map, which is second, invisible canvas element. In this, each county is a unique color derived from it's index in the feature list. Using getImageData(), you can get the bit map of the canvas. Then, you can use it to test whether your random, bounding box constrained coordinates fall within the county by checking the color of the colormap at that coordinate. This test is a O(1) operation, where as yours looks like it's O(n).
I am using this technique to create a dot density map of counties in china, and the performance is fine. I started with this guy's code example:
https://gist.github.com/awoodruff/94dc6fc7038eba690f43