Using Sequelize and geospatial queries, if I want to find the "n" closest points to a certain location, how should the Sequelize query be?
Assume I have a model that looks something like this:
sequelize.define('Point', {geo: DataTypes.GEOMETRY('POINT')});
Now let's say we input 100 random points in the db through something like:
db.Point.create({geo: {type: 'Point', coordinates: [randomLng,randomLat]}});
Imagine we have a lat and lng variables to define a location, and we want to find the 10 closest points to it. when I run this query I get an error:
const location = sequelize.literal(`ST_GeomFromText('POINT(${lat} ${lng})', 4326)`);
db.Point.findAll({
attributes: [['distance', sequelize.fn('ST_Distance', sequelize.col('Point'), location)]],
order: 'distance',
limit: 10
});
// -> TypeError: s.replace is not a function
Any idea what is the issue / how to fix it?
Thx!
MySQL can give an error that function ST_Distance_Sphere does not exist. In that case you can use this alternative solution:
I hold point information separately as latitude and longitude decimals. Assume you should have a model that looks something like this:
sequelize.define('Point', {latitude: DataTypes.DECIMAL(11,2)},
{longitude: DataTypes.DECIMAL(11,2)});
Imagine we have a lat and lng variables to define a location, and we want to find the 10 closest points to it:
db.Point.findAll({
attributes: [[sequelize.fn('POW',sequelize.fn('ABS',sequelize.literal("latitude-"+lat)),2),'x1'],
[sequelize.fn('POW',sequelize.fn('ABS',sequelize.literal("longitude-"+lng)),2),'x2']],
order: sequelize.fn('SQRT', sequelize.literal('x1+x2')),
limit: 10
});
Update:
With Haversine Formula, distance is more accurate:
db.Point.findAll({
attributes: [[sequelize.literal("6371 * acos(cos(radians("+lat+")) * cos(radians(latitude)) * cos(radians("+lng+") - radians(longitude)) + sin(radians("+lat+")) * sin(radians(latitude)))"),'distance']],
order: sequelize.col('distance'),
limit: 10
});
When you surround sequelize.fn with brackets, you must also include a string as an alias:
[sequelize.fn('ST_Distance_Sphere', sequelize.literal('geolocation'), location), 'ALIASNAME']
Also, try changing ST_Distance to ST_Distance_Sphere. So:
const location = sequelize.literal(`ST_GeomFromText('POINT(${lng} ${lat})', 4326)`);
User.findAll({
attributes: [[sequelize.fn('ST_Distance_Sphere', sequelize.literal('geolocation'), location),'distance']],
order: 'distance',
limit: 10,
logging: console.log
})
.then(function(instance){
console.log(instance);
})
This is actually working for me.
obs: be sure you substitute 'User' with the model in which you have the geometry data type.
Update: If you still can't order using order: 'distance', maybe you should declare it in a var and use order: distance without quotes, like this:
var lat = parseFloat(json.lat);
var lng = parseFloat(json.lng);
var attributes = Object.keys(User.attributes);
var location = sequelize.literal(`ST_GeomFromText('POINT(${lng} ${lat})')`);
var distance = sequelize.fn('ST_Distance_Sphere', sequelize.literal('geolocation'), location);
attributes.push([distance,'distance']);
var query = {
attributes: attributes,
order: distance,
include: {model: Address, as: 'address'},
where: sequelize.where(distance, {$lte: maxDistance}),
logging: console.log
}
Update on distance accuracy:
The solution mentioned by sarikaya does seem to be more accurate. Here is how to do it using postgres:
var distance = sequelize.literal("6371 * acos(cos(radians("+lat+")) * cos(radians(ST_X(location))) * cos(radians("+lng+") - radians(ST_Y(location))) + sin(radians("+lat+")) * sin(radians(ST_X(location))))");
Building off #Edudjr's answer, this is what I did to get it to work in my project:
const location = sequelize.literal(`ST_GeomFromText('POINT(${ startLongitude } ${ startLatitude })')`)
const distance = sequelize.fn('ST_Distance_Sphere', sequelize.col('location'), location)
const inRadius = await Position.findAll({
order: distance,
where: sequelize.where(distance, { $lte: radius }),
logging: console.log
})
where Position is defined as:
sequelize.define('Position', {
location: DataTypes.GEOMETRY('POINT')
})
Note that Point requires the coordinates in the format of (longitude
latitude)
https://gis.stackexchange.com/questions/209008/incorrect-arguments-to-st-distance-sphere-in-special-cases
https://dba.stackexchange.com/questions/33410/whats-the-difference-between-pointx-y-and-geomfromtextpointx-y
Related
I have a FeatureCollection made up of many (100-200) polygons ('ftr_polygons'). I also have an ImageCollection made up of monthly median Landsat8 bands and indices ('byMonth'). I want to ReduceRegions and save a median (or mean) spatial average from each polygon in the FeatureCollection. End goal is to export to csv a timeseries of monthly mean bands/indices within each polygons over multiple years (2013-2019).
With the code below, I am able to do this for ~1 year, but any more than that, and I get an error: 'FeatureCollection (Error) Computation timed out’. Is there a better way to do this?
// define the function that will grab median (or mean) spatial reductions for each polygon, for each month
var extractdata = function(medianImage,ftr_polygons) {
var date_start = ee.Date(medianImage.get('system:time_start')).format("YYYY-MM"); // get date as string to append to each property
// spatial MEDIAN
ftr_polygons = medianImage.reduceRegions({ // create feature collection with new properties, bands for each month, uniquely named
collection: ftr_polygons,
reducer: ee.Reducer.median(),
scale: 30,
tileScale: 1}); // tile scale
var ftr_polygons_propnames = ftr_polygons.first().propertyNames(); // get property names first
var ftr_polygons_newnames = ftr_polygons_propnames.replace('NDVI_median',
ee.String('NDVI_median_').cat(date_start)); //replace property names with band+date
ftr_polygons_newnames = ftr_polygons_newnames.replace('EVI_median',
ee.String('EVI_median_').cat(date_start)); //replace property names with band+date
ftr_polygons_newnames = ftr_polygons_newnames.replace('NIRv_median',
ee.String('NIRv_median_').cat(date_start)) ; //replace property names with band+date
ftr_polygons = ftr_polygons.map(function(f) {return f.select(ftr_polygons_propnames,ftr_polygons_newnames)});
return ftr_polygons;
};
// apply the function over ImageCollection byMonth, beginning with feature collection ftr_polygons
var ftr_polygons = ee.FeatureCollection(byMonth.iterate(extractdata,ftr_polygons));
// remove geometry on each feature before printing or exporting
var myproperties=function(feature){
feature=ee.Feature(feature).setGeometry(null);
return feature;
};
var ftr_polygon_export = ftr_polygon.map(myproperties)
print(ftr_polygon_export.limit(1), 'For export w monthly properties');
Maybe this answer: https://stackoverflow.com/a/48412324/12393507 alludes to a better way:
The same approach can be used with reduceRegions() as well, mapping over images and then over regions. However, you will have to map over the resulting features to set dates.
I would appreciate more info on this approach.
Thanks.
For computationally intensive operations that will run for a long time you should always export your results instead of visualizing/printing them.
For more info read through this section of the debugging page in the Earth Engine manual.
I want to draw a routing line from Land of Canaan to Waipu but After I compile the line become like this,
What I expect is like this,
My code:
routing.forEach((obj, index) => {
const line_point = [];
obj.waypoints.forEach((pos, i) => {
const lng = Number(pos.lon);
const lat = Number(pos.lat);
line_point.push(ol.proj.fromLonLat([lng, lat]));
});
const routeLayer = new ol.layer.Vector({
source: new ol.source.Vector({
features: [new ol.Feature({
geometry: new ol.geom.LineString(line_point, 'XY'),
name: 'Line'
})]
}),
style: new ol.style.Style({
stroke: new ol.style.Stroke({
color: '#bc0000',
width: 2
})
})
});
routeLayer.setZIndex(55);
this.mapLayer.push(routeLayer);
this.map.addLayer(routeLayer);
});
May I know what happened to this and how to solve it?
One of your LineString ist crossing the antimeridian (180° W/E). A solution is to split the LineString at the antimeridian.
Your algorithm is have issues. You have to consider split the line in as many chunks as be needed. As you did not provided the code, I am able only to include cases in conceptual way.
Your array variable could be an array of arrays with all line chunks needed to draw.
line_point
Includes a draw with border cases:
Line a-b is similar to your case with two chunks.
Line c-d is other case with two chunks.
Line e-f is an complex case where there is three chunks.
The dotten light green line represent the union between the chunks
Below is the code snippet for a barchart with colored bars:
var Dim2 = ndx.dimension(function(d){return [d.SNo, d.something ]});
var Group2 = Dim2.group().reduceSum(function(d){ return d.someId; });
var someColors = d3.scale.ordinal().domain(["a1","a2","a3","a4","a5","a6","a7","a8"])
.range(["#2980B9","#00FFFF","#008000","#FFC300","#FF5733","#D1AEF1","#C0C0C0","#000000"]);
barChart2
.height(250)
.width(1000)
.brushOn(false)
.mouseZoomable(true)
.x(d3.scale.linear().domain([600,800]))
.elasticY(false)
.dimension(Dim2)
.group(Group2)
.keyAccessor(function(d){ return d.key[0]; })
.valueAccessor(function(d){return d.value; })
.colors(someColors)
.colorAccessor(function(d){return d.key[1]; });
How do I add a legend to this chart?
Using composite keys in crossfilter is really tricky, and I don't recommend it unless you really need it.
Crossfilter only understands scalars, so even though you can produce dimension and group keys which are arrays, and retrieve them correctly, crossfilter is going to coerce those arrays to strings, and that can cause trouble.
Here, what is happening is that Group2.all() iterates over your data in string order, so you get keys in the order
[1, "a1"], [10, "a3"], [11, "a4"], [12, "a5"], [2, "a3"], ...
Without changing the shape of your data, one way around this is to sort the data in your legendables function:
barChart2.legendables = function() {
return Group2.all().sort((a,b) => a.key[0] - b.key[0])
.map(function(kv) {
return {
chart: barChart2,
name: kv.key[1],
color: barChart2.colors()(kv.key[1]) }; }) };
An unrelated problem is that dc.js takes the X domain very literally, so even though [1,12] contains all the values, the last bar was not shown because the right side ends right at 12 and the bar is drawn between 12 and 13.
So:
.x(d3.scale.linear().domain([1,13]))
Now the legend matches the data!
Fork of your fiddle (also with dc.css).
EDIT: Of course, you want the legend items unique, too. You can define uniq like this:
function uniq(a, kf) {
var seen = [];
return a.filter(x => seen[kf(x)] ? false : (seen[kf(x)] = true));
}
Adding a step to legendables:
barChart2.legendables = function() {
var vals = uniq(Group2.all(), kv => kv.key[1]),
sorted = vals.sort((a,b) => a.key[1] > b.key[1] ? 1 : -1);
// or in X order: sorted = vals.sort((a,b) => a.key[0] - b.key[0]);
return sorted.map(function(kv) {
return {
chart: barChart2,
name: kv.key[1],
color: barChart2.colors()(kv.key[1]) }; }) };
Note that we're sorting by the string value of d.something which lands in key[1]. As shown in the comment, sorting by x order (d.SNo, key[0]) is possible too. I wouldn't recommend sorting by y since that's a reduceSum.
Result, sorted and uniq'd:
New fiddle.
Cocos Creator - I have a node that I want to rotate towards another node, here is the code I'm using:
update: function (dt) {
this.rotate();
},
rotate: function () {
var diff = this.target.position - this.node.position;
var angle = Math.atan2(diff.x, diff.y);
this.node.rotation = cc.radiansToDegress(angle);
},
But it's not rotating at all, I tried to search the docs but couldn't find anything helpful.
var diff = this.target.position - this.node.position;
You're basically trying to subtract an object from an object. Check
{'x':2, 'y':3} - {'x':4, 'y':6}
in your JS console. The result is NaN
You need to subtract each dimension manually.
var diff = {
'x' : this.target.position.x - this.node.position.x,
'y':this.target.position.y - this.node.position.y
};
Apologies to necro this question, but since this is a top search result I just wanted to add that the problem here is that the signature of Math.atan2 takes it's coordinates backwards:
Syntax:
Math.atan2(y, x)
Source: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/atan2
I'm trying to render a google map from data I have in a LocationCollection.
Users will define filterParameters in a Model, here is the JSON from a filter
var filter = {
"id_ref_type_category":[2,4],
"pricemin":5,
"pricemax":15,
"distmin":10, // meters
"distmax":150 // meters
}
I need to query my collection, here is a sample of json I have for my location
var location = [
{
"id":"1",
"name":"Sushi 1",
"price_min":"10",
"price_max":"20",
"price_avg":"15",
"id_ref_type_category":"1",
"latitude":"48.863831",
"longitude":"2.356215"
},
{
"id":"2",
"description":"Miam ",
"price_min":"15",
"price_max":"35",
"price_avg":"25",
"id_ref_type_category":"4",
"latitude":"48.864269",
"longitude":"2.355153"
},
{
"id":"3",
"name":"Restaurant 1",
"price_min":"5",
"price_max":"20",
"price_avg":"12.5",
"street_number":"60",
"id_ref_type_category":"1",
"latitude":"48.863407",
"longitude":"2.350938"
},
{
"id":"4",
"name":"Chez gigi",
"price_min":"0",
"price_max":"17",
"price_avg":"8.5",
"id_ref_type_category":"2",
"latitude":"48.861824",
"longitude":"2.350901"
}
]
Regarding to my filter parameter, i am looking for
a location with a id_ref_type_category equal 2 OR 4
a location with an average price around 5 and 15
a location within a distance around 10 and 150 (it's in meters) (if user allow geolocation)
I can calculate the distance from my position to the location with this js function from geocoding http://www.geodatasource.com/developers/javascript
I have looked for backbone collection filtering but didn't find a lot, I have looked for json query systems, but couldn't find any stuff.
I takes any suggestions.
How about applying a underscore filter for all the attributes like in here:
http://jsfiddle.net/cnDeu/1/
I have relaxed a bit your filter object so that a location makes it through.
Essentially the filter looks like this:
var filtered = loc.filter(function (el) {
var dist = distance(el.get('latitude'), el.get('longitude'),
position.latitude, position.longitude, 'K') / 1000;
return ((el.get('id') == filter.id_ref_type_category[0]) ||
(el.get('id') == filter.id_ref_type_category[1])) &&
el.get('price_avg') >= filter.pricemin &&
el.get('price_avg') <= filter.pricemax &&
dist >= filter.distmin &&
dist <= filter.distmax;
});