Reputation: 32893
I am writing a small app. I have following data
{
"feat": [
{
"type": "Feature",
"id": "AFG",
"properties": {
"name": "Afghanistan"
},
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
61.210817,
35.650072
],
[
64.546479,
36.312073
],
[
64.746105,
37.111818
],
[
65.588948,
37.305217
],
[
65.745631,
37.661164
],
[
66.217385,
37.39379
],
[
66.518607,
37.362784
],
[
67.075782,
37.356144
],
[
67.83,
37.144994
],
[
68.135562,
37.023115
],
[
68.859446,
37.344336
],
[
69.196273,
37.151144
],
[
69.518785,
37.608997
],
[
70.116578,
37.588223
],
[
70.270574,
37.735165
],
[
71.262348,
36.074388
],
[
71.498768,
35.650563
],
[
71.613076,
35.153203
],
[
71.115019,
34.733126
],
[
71.156773,
34.348911
],
[
70.881803,
33.988856
],
[
69.930543,
34.02012
],
[
70.323594,
33.358533
],
[
69.687147,
33.105499
],
[
69.262522,
32.501944
],
[
69.317764,
31.901412
],
[
68.926677,
31.620189
],
[
68.556932,
31.71331
],
[
67.792689,
31.58293
],
[
60.52843,
33.676446
],
[
60.803193,
34.404102
],
[
61.210817,
35.650072
]
]
]
}
},
{
"type": "Feature",
"id": "AGO",
"properties": {
"name": "Angola"
},
"geometry": {
"type": "MultiPolygon",
"coordinates": [
[
[
[
16.326528,
-5.87747
],
[
16.57318,
-6.622645
],
[
16.860191,
-7.222298
],
[
17.089996,
-7.545689
],
[
17.47297,
-8.068551
],
[
21.801801,
-8.908707
],
[
21.875182,
-9.523708
],
[
22.208753,
-9.894796
],
[
22.155268,
-11.084801
],
[
22.402798,
-10.993075
],
[
22.837345,
-11.017622
],
[
23.456791,
-10.867863
],
[
23.912215,
-10.926826
],
[
24.017894,
-11.237298
],
[
23.904154,
-11.722282
],
[
24.079905,
-12.191297
],
[
23.930922,
-12.565848
],
[
24.016137,
-12.911046
],
[
21.933886,
-12.898437
],
[
21.887843,
-16.08031
],
[
22.562478,
-16.898451
],
[
23.215048,
-17.523116
],
[
21.377176,
-17.930636
],
[
18.956187,
-17.789095
],
[
18.263309,
-17.309951
],
[
12.175619,
-14.449144
],
[
12.500095,
-13.5477
],
[
12.738479,
-13.137906
],
[
13.312914,
-12.48363
],
[
13.633721,
-12.038645
],
[
13.738728,
-11.297863
],
[
13.686379,
-10.731076
],
[
13.387328,
-10.373578
],
[
13.120988,
-9.766897
],
[
12.87537,
-9.166934
],
[
13.375597,
-5.864241
],
[
16.326528,
-5.87747
]
]
],
[
[
[
12.436688,
-5.684304
],
[
12.182337,
-5.789931
]
]
]
]
}
},
{
"type": "Feature",
"id": "ALB",
"properties": {
"name": "Albania"
},
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
20.590247,
41.855404
],
[
19.406082,
40.250773
],
[
19.319059,
40.72723
],
[
19.40355,
41.409566
],
[
19.540027,
41.719986
],
[
19.371769,
41.877548
],
[
19.304486,
42.195745
],
[
19.738051,
42.688247
],
[
19.801613,
42.500093
],
[
20.0707,
42.58863
],
[
20.283755,
42.32026
],
[
20.52295,
42.21787
],
[
20.590247,
41.855404
]
]
]
}
}
]
}
What happens is that user will provide latitude
and longitude
. Then I need to find if those values are present in above dataset. if it is then return that country.
The problem is that above dataset coordinates array is different. Sometimes it is 3 dimensional and sometimes it is 2.It is keep changing. I am not sure how can I find efficient solution for this.
Below is some of my code which currently it prompts user from latitude and longitude and and loop through data.
const rl = require('readline').createInterface(process.stdin, process.stdout);
const coordinates = require('./data.json').feat;
var prompts = ['Enter coordinates (lat, lon)'],
counter = 0;
// console.log(countries);
rl.on('line', (line) => {
if (!line) {
console.log('Enter values.');
}
let lat = line.split(' ')[0],
lon = line.split(' ')[1];
console.log(lat, lon);
coordinates.forEach((data, index) => {
console.log(data.geometry.coordinates[index]);
data.geometry.coordinates.forEach((geo, i) => {
// console.log(geo[i]);
});
});
get();
}).on('close', () => {
});
function get() {
rl.setPrompt(prompts[counter] + ': ');
rl.prompt();
}
get();
Any help in finding given longitude and latitude in above dataset?
Upvotes: 2
Views: 817
Reputation: 350365
You can detect the number of polygons by checking the type
property. If it is MultiPolygon
, you'll have one more nested level.
Here are some functions you could use to determine whether a point is in a polygon. The rest (lower part) of the snippet allows to test the getCountry
function: it performs the map drawing, mouse move handling and showing the country which the mouse moves over. But the essence is in the three functions at the top.
This is plain JS, based on the winding algorithm presented in this article but you might want to consider using a library for that instead, like d3.js.
function isLeft(p0, p1, p2) {
return ( (p1[0] - p0[0]) * (p2[1] - p0[1])
- (p2[0] - p0[0]) * (p1[1] - p0[1]) );
}
function inPoly(p, coord) {
var winding = 0;
// loop through all edges of the polygon
for (var i=0; i<coord.length-1; i++) {
if (coord[i][1] <= p[1]) {
if (coord[i+1][1] > p[1]) // an upward crossing
if (isLeft(coord[i], coord[i+1], p) > 0) // p left of edge
++winding; // have a valid up intersect
} else { // start y > P.y (no test needed)
if (coord[i+1][1] <= p[1]) // a downward crossing
if (isLeft(coord[i], coord[i+1], p) < 0) // p right of edge
--winding; // have a valid down intersect
}
}
return winding;
}
function getCountry(p, data) {
// p: point represented by array with the two coordinates
var country;
return data.feat.some(function (obj) {
country = obj;
var polygons = obj.geometry.coordinates;
if (obj.geometry.type !== 'MultiPolygon') polygons = [polygons];
return polygons.some(function (polygon) {
return inPoly(p, polygon[0]);
});
}) ? country : null;
}
// I/O for this interactive snippet
function drawPolygon(ctx, coord, fillColor) {
ctx.fillStyle = fillColor;
ctx.beginPath();
coord.forEach(function (point, i) {
if (i==0) {
ctx.moveTo(point[0], point[1]);
} else {
ctx.lineTo(point[0], point[1]);
}
});
ctx.fill();
ctx.stroke();
// ctx.closePath();
}
function drawCountry(ctx, selectedCountry, country) {
var polygons = country.geometry.coordinates;
var color = selectedCountry == country ? 'yellow' : 'grey';
if (country.geometry.type !== 'MultiPolygon') polygons = [polygons];
polygons.forEach(function (polygon) {
drawPolygon(ctx, polygon[0], color);
});
}
function drawWorld(ctx, data, selectedCountry) {
data.feat.forEach(drawCountry.bind(null, ctx, selectedCountry));
}
// Define transformation so countries will be mapped to canvas area
var scale = [3, -3];
var move = [-20, 130];
var canvas = document.querySelector('canvas');
var ctx = canvas.getContext("2d");
ctx.translate(...move);
ctx.scale(...scale);
ctx.lineWidth = 1/scale[0];
// Sample data
var data = {"feat":[{"type":"Feature","id":"AFG","properties":{"name":"Afghanistan"},"geometry":{"type":"Polygon","coordinates":[[[61.210817,35.650072],[62.230651,35.270664],[62.984662,35.404041],[63.193538,35.857166],[63.982896,36.007957],[64.546479,36.312073],[64.746105,37.111818],[65.588948,37.305217],[65.745631,37.661164],[66.217385,37.39379],[66.518607,37.362784],[67.075782,37.356144],[67.83,37.144994],[68.135562,37.023115],[68.859446,37.344336],[69.196273,37.151144],[69.518785,37.608997],[70.116578,37.588223],[70.270574,37.735165],[70.376304,38.138396],[70.806821,38.486282],[71.348131,38.258905],[71.239404,37.953265],[71.541918,37.905774],[71.448693,37.065645],[71.844638,36.738171],[72.193041,36.948288],[72.63689,37.047558],[73.260056,37.495257],[73.948696,37.421566],[74.980002,37.41999],[75.158028,37.133031],[74.575893,37.020841],[74.067552,36.836176],[72.920025,36.720007],[71.846292,36.509942],[71.262348,36.074388],[71.498768,35.650563],[71.613076,35.153203],[71.115019,34.733126],[71.156773,34.348911],[70.881803,33.988856],[69.930543,34.02012],[70.323594,33.358533],[69.687147,33.105499],[69.262522,32.501944],[69.317764,31.901412],[68.926677,31.620189],[68.556932,31.71331],[67.792689,31.58293],[67.683394,31.303154],[66.938891,31.304911],[66.381458,30.738899],[66.346473,29.887943],[65.046862,29.472181],[64.350419,29.560031],[64.148002,29.340819],[63.550261,29.468331],[62.549857,29.318572],[60.874248,29.829239],[61.781222,30.73585],[61.699314,31.379506],[60.941945,31.548075],[60.863655,32.18292],[60.536078,32.981269],[60.9637,33.528832],[60.52843,33.676446],[60.803193,34.404102],[61.210817,35.650072]]]}},{"type":"Feature","id":"AGO","properties":{"name":"Angola"},"geometry":{"type":"MultiPolygon","coordinates":[[[[16.326528,-5.87747],[16.57318,-6.622645],[16.860191,-7.222298],[17.089996,-7.545689],[17.47297,-8.068551],[18.134222,-7.987678],[18.464176,-7.847014],[19.016752,-7.988246],[19.166613,-7.738184],[19.417502,-7.155429],[20.037723,-7.116361],[20.091622,-6.94309],[20.601823,-6.939318],[20.514748,-7.299606],[21.728111,-7.290872],[21.746456,-7.920085],[21.949131,-8.305901],[21.801801,-8.908707],[21.875182,-9.523708],[22.208753,-9.894796],[22.155268,-11.084801],[22.402798,-10.993075],[22.837345,-11.017622],[23.456791,-10.867863],[23.912215,-10.926826],[24.017894,-11.237298],[23.904154,-11.722282],[24.079905,-12.191297],[23.930922,-12.565848],[24.016137,-12.911046],[21.933886,-12.898437],[21.887843,-16.08031],[22.562478,-16.898451],[23.215048,-17.523116],[21.377176,-17.930636],[18.956187,-17.789095],[18.263309,-17.309951],[14.209707,-17.353101],[14.058501,-17.423381],[13.462362,-16.971212],[12.814081,-16.941343],[12.215461,-17.111668],[11.734199,-17.301889],[11.640096,-16.673142],[11.778537,-15.793816],[12.123581,-14.878316],[12.175619,-14.449144],[12.500095,-13.5477],[12.738479,-13.137906],[13.312914,-12.48363],[13.633721,-12.038645],[13.738728,-11.297863],[13.686379,-10.731076],[13.387328,-10.373578],[13.120988,-9.766897],[12.87537,-9.166934],[12.929061,-8.959091],[13.236433,-8.562629],[12.93304,-7.596539],[12.728298,-6.927122],[12.227347,-6.294448],[12.322432,-6.100092],[12.735171,-5.965682],[13.024869,-5.984389],[13.375597,-5.864241],[16.326528,-5.87747]]],[[[12.436688,-5.684304],[12.182337,-5.789931],[11.914963,-5.037987],[12.318608,-4.60623],[12.62076,-4.438023],[12.995517,-4.781103],[12.631612,-4.991271],[12.468004,-5.248362],[12.436688,-5.684304]]]]}},{"type":"Feature","id":"ALB","properties":{"name":"Albania"},"geometry":{"type":"Polygon","coordinates":[[[20.590247,41.855404],[20.463175,41.515089],[20.605182,41.086226],[21.02004,40.842727],[20.99999,40.580004],[20.674997,40.435],[20.615,40.110007],[20.150016,39.624998],[19.98,39.694993],[19.960002,39.915006],[19.406082,40.250773],[19.319059,40.72723],[19.40355,41.409566],[19.540027,41.719986],[19.371769,41.877548],[19.304486,42.195745],[19.738051,42.688247],[19.801613,42.500093],[20.0707,42.58863],[20.283755,42.32026],[20.52295,42.21787],[20.590247,41.855404]]]}}]};
// Show map on canvas
window.onload = drawWorld.bind(null, ctx, data, null);
// Handle mouse move to mention the matching country
canvas.onmousemove = function (e) {
// Get mouse coordinates relative to canvas
var x = e.pageX - this.offsetLeft;
var y = e.pageY - this.offsetTop;
// Perform the reverse of the canvas transformation
x = (x - move[0]) / scale[0];
y = (y - move[1]) / scale[1];
// Get country that contains this point
var country = getCountry([x, y], data);
var countryName = country ? country.properties.name : 'no match';
// Output the result
document.getElementById('country').textContent = countryName;
// Highight country
drawWorld(ctx, data, country)
};
canvas { border:1px solid; float: left }
<canvas width="250", height="200"></canvas>
<div id="country">Hover mouse...</div>
When you have a large list of polygons in your data set, it really is a waste of time to check each individual edge of each polygon to determine where the point fits.
It will be a major improvement if you would preprocess the data and add box information to each polygon: a square that encompasses the polygon completely ([minx,miny]-[maxx,maxy]
). When you need to match a point, you would first match against this box, and if it is outside, you can skip that polygon, saving time spent on it. There will normally only be a few of those boxes that encompass your point, and so you would only need to test against those few polygons.
Call the getCountry
function like it is done in the above snippet, where you pass the user defined x
and y
(i.e. longitude, latitude) as an array, and the data
object as the second argument:
var country = getCountry([x, y], data);
var countryName = country ? country.properties.name : 'no match';
Upvotes: 2
Reputation: 631
Your coordinates
is just another nested coordinate array in case of multiploygon but the structure is same so try something like this:
var list = feat;//feat has the json response
list.forEach(function(val, index)
{
var temp = val.geometry;
var set = [];
var type = temp.type.toLowerCase();//just for sake of simplicity
var coords = [];
if(type=='polygon')
coords = temp.coordinates[0];//which is an array of long-lats
else
{
var a = [];
temp.coordinates.forEach(function(coord)
{
a.concat(coord[0]);//note that the 0th index of each array has the list of long-lats
})
coords = a;
}
temp.coordinates = coords;
});
coordinates
key of each object should now have a 1D array of list of lat-longs. I haven't tested this code so please do check it. You can test each index of coordinates
for a given pair of lat-long. Please note that the above code just simplifies your response structure, and does not perform an actual search.
Upvotes: 0