How to implement Haversine Formula into Shopware 6 DAL? - mysql

I have a custom Shopware 6 Entity called Location with lat and lng fields which are storing coordinates.
I want to use the Shopware DAL since the docs say that you should always use DAL for Store API routes and I am trying to do that in a Store API route.
Is there any way with the regular Shopware DAL to implement the Haversine formula?
This is the formula in plain SQL:
SELECT id, ( 6371 * acos( cos( radians(input_latitude) ) * cos( radians(
latitude ) )
* cos( radians( longitude ) - radians(100.56133310918271) ) + sin( radians(input_longitude) ) * sin(radians(input_latitude)) ) ) AS distance
FROM location
HAVING distance < 1
ORDER BY distance
LIMIT 0 , 500;

As already stated in the comments to your question, doing complex computations like this with the DAL can't be done without altering some of its inner workings. That's just the nature of pretty much any abstraction layer.
While it is true that it is recommended to use the DAL in any case possible, I think it's absolutely fair game to use "raw" SQL when the limits of the abstraction layer have been reached. When you persist data using the DAL, events are emitted that allow the system and third-party developers to react to changes. That is one of the main aspects of why using the DAL is recommended. While reading data using the DAL will also emit events, they're not as critical to the overall architecture comparatively.
I would recommend trying to pre-select the ids and distances of your table using a plain SQL query. Try to keep it as simple and performant as possible. Then use the pre-selected ids with the DAL repository of your entity and fetch the full data sets like that. You might also want to add extensions to the entity instances to enrich them with the distance, in case you might need it. Then at least the data loading events for the actual entities will still be dispatched.
$idDistances = $this->connection->fetchAllAssociative(
'SELECT LOWER(HEX(id)) AS id, (...) AS distance FROM ...'
);
$ids = array_column($idDistances, 'id');
$entities = $this->repository->search(new Criteria($ids), $context)->getEntities();
$sortedEntities = [];
foreach ($idDistances as $idDistance) {
[$id, $distance] = $idDistance;
$entity = $entities->get($id);
if (!$entity) {
continue;
}
$textStruct = new TextStruct();
$textStruct->setContent($distance);
$entity->addExtension('distance', $textStruct);
$sortedEntities[] = $entity;
}

Related

Spatial functions and Google Maps

I have a Google Map where I'd like to display some markers etc.
If you open the website, it sets session to "geometrycollection empty". When the map is fully loaded, it makes an AJAX request with NE & SW lat and lng as parameters.
Handle method for AJAX looks like that:
$contain = $this->locationsManager->getContainOfAreas($this->getSession("union")->area, $polygon);
$area = $this->locationsManager->getDifferentiatedArea($polygon, $this->getSession("union")->area);
$coordinates = $this->locationsManager->getLocationsCoordinates($area["polygon"]);
$this->getSession("union")->area = $this->locationsManager->getUnionOfAreas($this->getSession("union")->area, $area["polygon"])["unionArea"];
But when I try to find out if area B ($this->getSession("union")->area - union of areas (viewports) user "visited") contains area A ($polygon - current viewport in Google Maps), it returns wrong results (I drew polygons from the variables via Google Maps Polygons and they were different).
This is SQL command for getting result of getContainOfAreas:
SELECT ST_CONTAINS(ST_GEOMFROMTEXT(?), ST_GEOMFROMTEXT(?)) AS result;
Do you know what I'm doing wrong?

Get all coords within radius in google maps

The thing is I'm trying to query my database to find all points that fall within a radius of a certain given point.
One possible way of doing this would be to get all the data on the database and then find the distance between those points and the point I'm interested in, but I have 36k records in that database and that would mean 36k google maps requests, which I understand wouldn't be possible with the free API.
What I was thinking is getting all possible coords in a radius of that point I'm interested in and check if any of the points in the database match those coords. Is that even possible? Or efficient? I suppose I would get a LOT of points and it would translate into a very long for loop, but I can't think of any other way.
Any suggestions?
EDIT
Ok, I'll give a little more detail as of the specific scenario as I forgot to do so and now several other problems came to my mind.
First off I'm using mongodb as a database.
Secondly, my database has the locations in UTM format (this is supposed to work only in zone 21s) whereas I'm handling client side coords with Google Map's LatLng coords. I'm converting the UTM coords to LatLng to actually use them in the map.
Haversine won't do it in this scenario would it?
Look into using the Haversine formula - if you have latitude and longitude in your database then you can use the Haversine formula in a SQL query to find records within a certain distance.
This article covers it with more details: http://www.plumislandmedia.net/mysql/haversine-mysql-nearest-loc/
Do you have latitude and longitude as separate numerical fields in your database? You could search for all points in your database that are in a square area whose sides are twice the radius of the circle you're looking for. Then just check the distances within that subset of points.
select * from points where lat > my_latitude-radius and lat < my_latitude + radius and long > my_longitude-radius and long < my_longitude+radius;
Here I have done with mongoose. We need to create a schema that contain type and coordinates. You can see more details in https://mongoosejs.com/docs/geojson.html
So it's has another problem with mongoose version. Mongoose v6.3.0
worked for me. When you will use countDocuments with the query, it can
be generate error but count function not generating any error. I know
count deprecated, it shouldn't be use but I haven't find better
solution. If anyone find solution for count, let me know. Also you can visit https://github.com/Automattic/mongoose/issues/6981
const schema = new mongoose.Schema(
{
location: {
type: {
type: String,
enum: ["Point"],
},
coordinates: {
type: [Number],
index: "2dsphere",
},
},
},
{ timestamps: true }
);
const MyModel = mongoose.model("rent", schema);
The query will be
const result = await MyModel.find({
location: {
$near: {
$geometry: {
type: "Point",
coordinates: [Number(filters.longitude), Number(filters.latitude)],
},
$maxDistance: filters.maxRadius * 1000,
$minDistance: filters.minRadius * 1000,
},
},
})

Issue With Converting Garmin GPS Reading Into Suitable Format For Google Earth API

I currently have the task of integrating some GPS data stored in a MySQL
database with google earth. The objective is to create
placemarks/waypoints of these readings and display them on google
earth.
I googled the task and came across an article :
"A Database Driven Earth App: Using PHP & MySQL with the Earth API" .
Located at the URL:
https://developers.google.com/earth/articles/phpsqlearth
I followed it successfully; until I came to where I had to create the
placemarks. The main issue is that the 'createPlacemark' function
has the following signature:
"createPlacemark(name,address,type,lat,lng)" .
My main point of concern is the lat and lng arguments (latitude and longitude), because the GPS data in the
database are all in the format :
"N5 bb.xxx E8 cc.yyy".
No separate longitude or latitude data was stored. The data is being
collected via a garmin gps.
I was thinking that perhaps I could resolve this issue by
doing this:
var point = ge.createPoint('N5 bb.xxx E8 cc.yyy ') ,
and forget about the
point.setLatitude(parseFloat(lat))
and
point.setLongitude(parseFloat(lng)) statements.
However , I wanted to confirm if I was on the right path seeing I will be away from my development machine for a few days.
No, calling the GEPlugin method createPoint like you have it
var point = ge.createPoint('N5 bb.xxx E8 cc.yyy');
would create a point with the ID N5 bb.xxx E8 cc.yyy - the createPoint method only takes a single string parameter and that is used to set the ID of the object.
As you have it the resultant point KML would look like this:
<Point id="N5 bb.xxx E8 cc.yyy">
</Point>
You would need to call one or more methods on the actual point object that is created to set the latitude and longitude data. Either point.set() or point.setLatitude() and point.setLongitude() - you would then finally set the point to the placemarks geometry for it to work.
looking at it all you really need to do is to parse the Garmin GPS using a simple function. Simply splitting the string using white-space should work fine.
//parse a string in the Garmin format "N5 bb.xxx E8 cc.yyy"
//returns a kmlpoint
function gpsToPoint(data) {
var parts = data.split(' ');
var point = ge.createPoint('');
point.setLatitude(parseFloat(parts[1]));
point.setLongitude(parseFloat(parts[3]));
return point;
}
Then just change the createPlacemark function so that you create the point object with the new gpsToPoint() method passing in the the Garmin data.
This would give you KML like
<Point>
<coordinates>bb.xxx,cc.yyy,0</coordinates>
</Point>

Using CakePHP to query a database of coordinates to return JSON for Google Map

I have the following example: http://dev.driz.co.uk/googlemap/ that represents the mapping functionality of an app I'm building in CakePHP (note this example isn't using Cake).
Using JSON, I'm populating the Google Map with some markers and also using Geolocation to center the map on your location, essentially so you can see posts around you. So in the real life app that JSON file will be dynamically built using Cake from a database.
What I want to do is using the coordinates of your location (the user) query the database to find posts that are say within a 10 mile radius of those coordinates. And pull say 10 posts out... I then want to make it so that you can drag the map around and load in more posts as you extend your radius if that makes sense. The problem is how to pass the coordinates/10 miles radius back to CakePHP to query the posts in the DB!
Are there any examples of this? And could someone post some code samples that could help to get me started building such a query? I have a query in place for pulling out 10 posts from the database, but it doesn't search for posts based on coordinates... How would I do that within a set 10 mile radius for a set of coordinates?
An example of the query is:
public function gMapTest() {
$posts = $this->Post->find('all', array('limit'=>10,'conditions'=>array('Post.status'=>array(1,2)),'order'=>array('Post.datetime'=>'desc'),
'contain'=>array('User'=>'Profile')));
$this->set('posts',$posts);
$this->set('_serialize', 'posts');
}
Use the Geocoder behavior on your model:
$this->Post->Behaviors->attach('Tools.Geocoder');
$this->Post->setDistanceAsVirtualField($lat, $lng);
$options = array('order' => array('Post.distance' => 'ASC'), 'limit' => 10);
$posts = $this->Post->find('all', $options);
http://www.dereuromark.de/2012/06/12/geocoding-with-cakephp/

How To Find Quad/Name/Scale From USGS Terra Server

I'm currently working on developing a Google Maps API implementation that overlays topographic data from USGS Terra Server. I think I have it pretty much under hand except that I can't figure out how to determine the name of the quad, name, & scale for the current tile being served from Terra Server. If you check out this site and zoom into the map that information is being displayed so it must be possible:
http://www.trails.com/topomap.aspx?trailid=fgu003-087
Here are links to some articles which explains more how the images are named by Terra Server:
About MSR Maps
STANDARDIZED DATA SET NAMES FOR DRG PRODUCTS
I'm hoping that some geoloc expert out there has already done this and can point me in the right direction. I'd appreciate if you could give me any clues how I might determine this information from the current map view when overlaying the USGS topo data over Google Maps to produce a user experience much like that of the example map about.
Thanks in advance for your help!
You can use the OGC Style Web Map Server Microsoft also hosts. These have a relatively simple lat/lon/scale structure for fetching data, rather than leaving you guessing about the numbers. Here is a url for Aerial. The Scale variable s ranges from 11-21. The t variable lets you choose between Aerial and Topos. Set t=2 for Topos - here is Topo URL.
To get the quad name and map reference etc. You will have to index the topos and build a database. If you have the Topos on a CD and they are in Tiff format you can use GDALTindex to build this index. Beyond this your queries reduce to Point-in-Polygon type, which you can perform using Net Topology Suite.
Since there is no simple intuitive mapping for all the different map-sets and scales, a precomputed index will be the best way to go.
Gdaltindex can index tif files and produce an index in Shapefile format. This can be mapped into MySQL Geometries using Ogr MySQL support.
In the example, the trails.com server is delivering the custom tile images through their own CDN and displaying those tiles over top of Google Maps using a .NET WebHandler.
Since you need the data to come from MSRMaps.com and not [Trails.com][3], you will point the MSRMaps.com WebHandler instead.
Below is how trails is doing it. Replace the getTileUrl function with something that makes a call to the msrmaps.com server instead, such as MSR Tile Link
var layer = new GTileLayer(new GCopyrightCollection(''), 1, 21);
layer.getTileUrl = function(a, b)
{
var ll = G_NORMAL_MAP.getProjection().fromPixelToLatLng(new GPoint(a.x * 256, (a.y + 1) * 256), b);
var ur = G_NORMAL_MAP.getProjection().fromPixelToLatLng(new GPoint((a.x + 1) * 256, a.y * 256), b);
return "http://cdn-www.trails.com/services/TopoGetGoogleMapTile.ashx?z=" + b + "&style=drgsr&ll=" + ll.y + "," + ll.x + "&ur=" + ur.y + "," + ur.x;
}
var map = new GMap2(document.getElementById("map"));
map.setCenter(new GLatLng(37.4419, -122.1419), 13);
map.setUIToDefault();
var mapType = new GMapType([layer], G_NORMAL_MAP.getProjection(), 'Trails', { errorMessage: google.mapError, textColor: "white", linkColor: "white" });
map.addMapType(mapType);
map.setMapType(mapType);