I want to sort array/object of cities based on my location from nearest (to my location) to furthest
I have list of locations that i get from database
How would i solve this using javascript and HTML5 geolocation?
I have something like this:
example :
var locations= [{"name":"location1" "latitude" :"31.413165123"
"longitude":"40.34215241"},{"name":"location2" "latitude" :"31.413775453"
"longitude":"40.34675341"}]
and I want to sort those location by nearest to my location
FIRST: The provided array is broken (i added commas between fields).
var locations = [{
"name": "location1",
"latitude": "31.413165123",
"longitude": "40.34215241"
}, {
"name": "location2",
"latitude": "31.413775453",
"longitude": "40.34675341"
}];
You'll need to leverage a custom sort function, which needs to return 1, -1, or 0 based on comparing 2 items.
var myLong = 42.0; // whatever your location is
var myLat = 3.16; // whatever your location is
locations.sort( function (a, b) {
// This is untested example logic to
// help point you in the right direction.
var diffA = (Number(a.latitude) - myLat) + (Number(a.longitude) - myLong);
var diffB = (Number(b.latitude) - myLat) + (Number(b.longitude) - myLong);
if(diffA > diffB){
return 1;
} else if(diffA < diffB){
return -1;
} else {
return 0; // same
}
} );
Store your location, create a function that calculates the distance between two points and then use the sort method:
function dist({latitude: lat1, longitude: long1}, {latitude: lat2, longitude: long2}) {
// I'm not very good at geography so I don't know how to calculate exactly the distance given latitudes and longitudes.
// I hope you can figure it out
// the function must return a number representing the distance
}
navigator.geolocation.getCurrentPosition(({coords}) => {
coords.latitude = parseFloat(coords.latitude)
corrds.longitude = parseFloat(coords.longitude)
locations.sort((p1, p2) => dist(coords, {latitude: parseFloat(p1.latitude), longitude: parseFloat (p1.longitude)}) -
dist(coords, {latitude: parseFloat(p2.latitude), longitude: parseFloat(p2.longitude)}))
})
Hope it helps you
Related
I am working on something to blacklist unwanted locations with the location service. Here is my current code:
How can I implement a blacklist feature?
if (navigator.geolocation) {
// Locate position
navigator.geolocation.getCurrentPosition(displayPosition, errorFunction);
} else {
alert('Your device location is not approved.');
}
// Success callback function
function displayPosition(pos) {
var mylat = pos.coords.latitude;
var mylong = pos.coords.longitude;
var thediv = document.getElementById('locationinfo');
alert('Your HWID is compliant of ProtoProt regulations.');
}
function errorFunction(pos) {
alert('Error: (PROTOPROT_POS_DENIED). We only use your HWID for checking compliance. Enable location to enter.');
}
Maintain a list of locations given by two points w,y.
Each w,y represent the two opposite points of a w,x,y,z square which represents the blacklisted location.
Each point has longitude and latitude coordinates, so w = [long, lat] and y = [long, lat]
With those, you can rebuild all the [long, lat] of all the w,x,y,z corners of your square, thus representing the blacklisted area.
Now, it's rather easy to know the boundaries of forbidden location: any point [long, lat] which is within the square is blacklisted.
You can store those values within a Javascript Object (a dictionary) which can be stored in a distinct ".js" file. JSON representation could look like:
blacklisted_areas = {
'area 51' : [w, y], // Replace w y with the floats of long and lat
'pink unicorn zoo' : [w, y], // same
// etc.
};
Access:
long = blacklisted_area['area 51'][0]
I have locations stored in mongodb that look like this in mongoose schema:
location: {
type: [Number],
index: '2d'
}
And on web client I'm using google maps api with custom radius resizer:
On each drag I'm making request with radius value that is calculated on client. Radius is the distance between pink arrow marker and center coordinates:
function metersToMiles (meters) {
return meters * 0.000621371192;
}
google.maps.event.addListener(this.sizer, 'dragend', () => {
const radius = this.circle.getRadius();
const radiusValue = Math.round(metersToMiles(radius) * 10) / 10; // convert to tenths (this is shown on UI as well)
this.props.onRadiusChange(radiusValue); // it's a react component
});
In mongodb request I'm using $nearSphere with radius in $maxDistance:
function milesToRadian(miles){
const earthRadiusInMiles = 3963.2;
return parseFloat(miles) / earthRadiusInMiles;
}
const { radius, lat, lon } = req.query;
const coords = (lat && lon) ? [lat, lon] : ukCoordinates;
const radians = milesToRadian(radius);
console.log('%s, %s, %s', radius, coords, radians);
// what I receive: 1.6, 0.00040371417036737993, [ 51.507351, -0.127758 ]
// ...
{
$nearSphere: coords,
$maxDistance: radians
}
However if you take a look on the gif image you'll see that there's some inaccuracy in results. For now I'm stucked with it, could you suggest what is the problem?
So the problem was really funny but still tricky. I've found out that in MongoDB you need to store coordinate pairs as [longitude, latitude] and not vice versa:
location: {
type: [Number], // longitude, latitude
index: '2d'
}
But in Google Maps SDK the order is different:
new google.maps.LatLng(lat, lng);
So to fix my problem I've needed to store coordinates in right order in MongoDB.
I have a set of values like this ones:
...
[
{
"LATITUDE": "40.1733",
"LONGITUDE": "-85.4786",
"EVENT_ID_COUNT": "2"
},{
"LATITUDE": "40.1733",
"LONGITUDE": "-85.4786",
"EVENT_ID_COUNT": "5"
}
]
Nokia Maps identify each record from this json as only one marker, but, what I need is to show the "EVENT_ID_COUNT" value, and add together them.
For example, if I am out, I should see one marker with value "7", but when I zoom in, I should see two markers, where one of them has "2" and the other "5"
This behavior helps me to have 100K markers, but only show 3K because several of them come from the same place.
The code:
....
function ZoomToTheme(){
var baseTheme = new nokia.maps.clustering.MarkerTheme();
this.getClusterPresentation= function(dataPoints){
var cluster = baseTheme.getClusterPresentation(dataPoints);
cluster.$boundingBox = dataPoints.getBounds();
return cluster;
}
this.getNoisePresentation = function(dataPoint){
var noisePoint = baseTheme.getNoisePresentation(dataPoint);
noisePoint.$text = dataPoint.text;
return noisePoint;
}
}
function addZoomToListener(map){
map.addListener("click", function(evt) {
if ( evt.target.$boundingBox !== undefined){
evt.display.zoomTo(evt.target.$boundingBox, false);
$("#ticker").text("");
} else if ( evt.target.$text !== undefined){
$("#ticker").text(evt.target.$text + " noise point was clicked.");
}
} );
}
....
data.forEach(function(d, index) {
if(d[mapOptions.count]){
dataPoints.push({
latitude : d.latitude,
longitude : d.longitude,
text : d.count
});
}
}, this);
...
this.clusterProvider = new nokia.maps.clustering.ClusterProvider(
this.map,
{
eps: 16,
minPts: 1,
dataPoints: dataPoints,
theme : new ZoomToTheme()
}
);
You can add the event count to each of the data points as 'weight' property (https://developer.here.com/javascript-apis/documentation/v3/maps/topics_api_nlp/h-clustering-datapoint.html). The result will be that they get counted together during the clustering. After the clustering operation your clusters should have the combined weight (https://developer.here.com/javascript-apis/documentation/v3/maps/topics_api_nlp/h-clustering-icluster.html). Use getWeight on the result to get the combined weight within a cluster...
What i have done so far:
i'm developing an application where i have to display more than(50K) points/Markers on the Navteq map divided into different segments.
for example: if i have 50K points i will divide all points into different segments.
if i divide 50K points into 50 segments each segment would have 1000 points (may not be 50 segments , it may depend).
right now it is working but it takes long time and hangs to render all the points on the MAP.so that i would like to perform segmentation displaying to display only few points with clustering.
so that i can get an idea of how the segment will look like.
but the problem here is i should only perform the clustering based on the segments.otherwise points from different segments willbe mixed together and displayed
as single unit and that conveys the wrong information to the user.
so here my question is: is it possible to perform the clustering based on the segment. so that only points from same segment will be clustered.
Note: if this is not possible, i would like to use Latest version of here-maps 2.5.3 (Asynchronous) may reduce some time while loading, so that i would like to use indexing functionality also while rendering the points
to improve the rendering time using nokia.maps.clustering.Index class.
i studied that indexing would reduce the time while rendering the points/markers on map. does it help in my case? could anybody please suggest how to perform indexing ?
This is the code with which i'm displaying points on map:
function displayAllLightPoints(arrLightPointCoordinats, totalLightPoints,
selectedSegmentId, totalSegmentsCount,segmentColorcode)
{
var MyTheme1 = function () {
};
segmentColorcode = segmentColorcode.substring(2,segmentColorcode.length-1);
MyTheme1.prototype.getNoisePresentation = function (dataPoint) {
var markerLightPoint = new nokia.maps.map.Marker(dataPoint, {
icon: new nokia.maps.gfx.BitmapImage("..//Images//Lightpoint//" +
segmentColorcode + ".png"),
anchor: {
x: 12,
y: 12
}
});
return markerLightPoint;
};
MyTheme1.prototype.getClusterPresentation = function (data) {
var markerLightPoint = new
nokia.maps.map.StandardMarker(data.getBounds().getCenter(), {
icon: new nokia.maps.gfx.BitmapImage("..//Images//
Segment/" + segmentColorcode + ".png", null, 66, 65),
text: data.getSize(),
zIndex: 2,
anchor: {
x: 12,
y: 12
}
});
return markerLightPoint;
};
var ClusterProvider = nokia.maps.clustering.ClusterProvider,
theme = new MyTheme1(),
clusterProvider = new ClusterProvider(map, {
eps: 0.00000000001,
minPts: 1000000,
strategy: nokia.maps.clustering.ClusterProvider.
STRATEGY_DENSITY_BASED,
theme: theme,
dataPoints: []
});
var lightpointsDataSet1 = new Array();
for (var i = 0; i < totalLightPoints; i++) {
lightpointsDataSet1[i] = { latitude: arrLightPointCoordinats[i][0],
longitude: arrLightPointCoordinats[i][1], title:
'LightPoint ' + (i + 1) };
}
clusterProvider.addAll(lightpointsDataSet1);
clusterProvider.cluster();
}
To deal with a very large (50K+) data set , I would do all the heavy number crunching server side and send over a new JSON response whenever the map is updated. Something like the HTML page described here
The key section of the code is the ZoomObserver:
var zoomObserver = function (obj, key, newValue, oldValue) {
zoom = newValue;
if (zoom < 7)
{ zoom = 7;}
if (zoom > 16)
{ zoom = 16;}
// Define the XML filename to read that contains the marker data
placeMarkersOnMaps('http://api.maps.nokia.com/downloads/java-me/cluster/'+ zoom + '.xml'
+ '?lat1=' + map.getViewBounds().topLeft.latitude
+ '&lng1='+ map.getViewBounds().topLeft.longitude
+ '&lat2='+ map.getViewBounds().bottomRight.latitude
+ '&lng2='+ map.getViewBounds().bottomRight.longitude);
};
map.addObserver("zoomLevel", zoomObserver );
Where the REST service returns a "well-known" data format which can be used to add markers and clusters to the map.
Now assuming you have two massive data sets you could make two requests to different endpoints, or somehow distinguish which cluster of data belongs to which so that you would just be returning information of the form:
{latitude':51.761,'longitude':14.33128,'value':102091},
i.e. using the DataPoint standard (which means you could use a heat map as well.
Of course, what I'm not showing here is the back-end functionality to cluster in the first place - but this leaves the client (and the API) to do what it does best displaying data, not number crunching.
I have an array of latitude/longitude values, rather like this:
var points = ["-0.15868039429188,51.534183502197", "-0.158839,51.534916", "-0.158814,51.53503", "-0.158817,51.535076", "-0.157492,51.535404", "-0.155767,51.535816", "-0.155696,51.535831", "-0.15526,51.535934", "-0.153192,51.536388", "-0.152282,51.536575", "-0.152467,51.536968", "-0.152592,51.53727", "-0.152682,51.53756", "-0.152074,51.53754", "-0.151921,51.537464", "-0.151732,51.538368", "-0.151373,51.538841", "-0.150622,51.539482", "-0.150237,51.539761", "-0.150047,51.539875", "-0.149957,51.539921", "-0.149594,51.540108", "-0.149563,51.540134", "-0.149536,51.540161", "-0.149497,51.540184", "-0.149445,51.540203"];
(OK, it's not strictly an array of tuples, but close enough.)
I want to find the four bounds of the array - i.e. the latitude and longitude north/west/south/east bounds.
Currently I'm doing this:
$.each(coords, function(index, value) {
var j = value.split(',');
var coord_lat = parseFloat(j[1]);
var coord_lng = parseFloat(j[0]);
if (coord_lat>nbound) {
nbound = coord_lat;
} else if (coord_lat<sbound) {
sbound = coord_lat;
}
if (coord_lng<ebound) {
ebound = coord_lng;
} else if (coord_lng>wbound) {
wbound = coord_lng;
}
});
However, this doesn't feel very efficient. Can anyone recommend a better way to do it?
If you aren't constrained to the current input format you could use objects instead. This will avoid the expensive split and parseFloat calls.
var points = [
{ latitude: -0.15868039429188, longitude: 51.534183502197 },
{ latitude: -0.158839, longitude: 51.534916 },
{ latitude: -0.155696, longitude: 51.535831 }
];
This is very small, but there is some unnecessary overhead in using jquery's each method. You're slightly better off here using a plain for loop on the array of points.
I think your longitude tests are back-to-front:
if (coord_lng < ebound) {
ebound = coord_lng;
Longitude increases eastward, so the < should be >.
In a system where longitude is expressed as +ve for east and -ve for west, just east of 180° is -179° and just west is +179°. Should ebound be +179 and wbound be -179 with an interval of 358°, or the other way around with an interval of 2°?