Axis rotation with an oriented bounding box - c++

I'm trying to put an oriented bounding box around the stamford rabbit for a project. To do this, I create a covariance matrix with the vertices and use the eigenvector columns as the new axis vectors for the OBB.
To draw the OBB, I take the cross product of the vector columns with the x,y and z axes to find the vector perpendicular both, then I use the dot product to find the angle between them.
//rv,uv,fv are the normalised column vectors from the eigenvector matrix.
// Calculate cross product for normal
crossv1x[0] = xaxis[1]*rv[2] - xaxis[2]*rv[1];
crossv1x[1] = xaxis[2]*rv[0] - xaxis[0]*rv[2];
crossv1x[2] = xaxis[0]*rv[1] - xaxis[1]*rv[0];
// Calculate cross product for normal
crossv2y[0] = yaxis[1]*uv[2] - yaxis[2]*uv[1];
crossv2y[1] = yaxis[2]*uv[0] - yaxis[0]*uv[2];
crossv2y[2] = yaxis[0]*uv[1] - yaxis[1]*uv[0];
// Calculate cross product for normal
crossv3z[0] = zaxis[1]*fv[2] - zaxis[2]*fv[1];
crossv3z[1] = zaxis[2]*fv[0] - zaxis[0]*fv[2];
crossv3z[2] = zaxis[0]*fv[1] - zaxis[1]*fv[0];
//dot product:
thetaX = dot(xaxis,rv,1)*180/PI;
thetaY = dot(yaxis,uv,1)*180/PI;
thetaZ = dot(zaxis,fv,1)*180/PI;
I then apply a rotation around the cross product vector with an angle determined by the dot product (glRotatef(angle,cross[0],cross1,cross[2]) for each axis). I then draw an axis aligned bounding box, then to the inverse rotation back to the original position.
glRotatef(thetaY,crossv2y[0],crossv2y[1],crossv2y[2]);
glRotatef(thetaZ,crossv3z[0],crossv3z[1],crossv3z[2]);
glRotatef(thetaX,crossv1x[0],crossv1x[1],crossv1x[2]);
glTranslatef(-meanX, -meanY, -meanZ);
glColor3f(1.0f,0.0f,0.0f);
AOBB(1); //Creates an axis aligned box.
glRotatef(-thetaX,crossv1x[0],crossv1x[1],crossv1x[2]);
glRotatef(-thetaZ,crossv3z[0],crossv3z[1],crossv3z[2]);
glRotatef(-thetaY,crossv2y[0],crossv2y[1],crossv2y[2]);
As you can see below, the box does not fit exactly onto the rabbit, nor does it align with the axis I have drawn... Am I missing something here? Ive fried my brain trying to find the solution but to no avail...

To draw the OBB, I take the cross product of the vector columns with the x,y and z axes to find the vector perpendicular both, then I use the dot product to find the angle between them.
Those "vector columns?" Those are actually the columns of the rotation matrix you want to generate.
So instead of using these vectors to compute rotation angles, just build a matrix with those (normalized) vectors as the columns. Upload it with glMultMatrix, and you should be fine.

Related

GeoDjango: How to create a circle anywhere on earth based on point and radius?

I have a similar question to this one. Using geodjango, I want to draw a circle on a map with a certain radius in km. However, the suggested solution
a) does not use km but instead degrees, and
b) becomes an oval further north or south.
Here is what I do:
from django.contrib.gis import geos
lat = 49.17
lng = -123.96
center = geos.Point(lng, lat)
radius = 0.01
circle = center.buffer(radius)
# And I then use folium to show a map on-screen:
map = folium.Map(
location=[lat,lng],
zoom_start=14,
attr="Mapbox"
)
folium.GeoJson(
circle.geojson,
name="geojson",
).add_to(map)
The result is this:
How can I
a) draw a circle that is always 3 km in radius, independent from the position on the globe, and
b) ensure this is a circle and not an oval at all latitudes?
Here is the Code
from django.contrib.gis import geos
import folium
lat = 49.17
lng = -123.96
center = geos.Point(x=lng, y=lat, srid=4326)
center.transform(3857) # Transform Projection to Web Mercator
radius = 3000 # now you can use meters
circle = center.buffer(radius)
circle.transform(4326) # Transform back to WGS84 to create geojson
# And I then use folium to show a map on-screen:
map = folium.Map(
location=[lat,lng],
zoom_start=14,
attr="Mapbox"
)
geojson = folium.GeoJson(
circle.geojson,
name="geojson",
)
geojson.add_to(map)
Explanation
This problem occurs due to Map Projections.
Lat/Long Coordinates are represented by the Map Projection WGS84. The Values are in degrees.
The map you see in folium has another map projection (Web Mercator). It tries to represent the world as a plane, which produces distortions to the north and south. The coordinate values are in meters.
On a globe your created circle would look completely round, but because folium uses another projection it gets distorted.
It is also important to know that every projection is represented by a number (EPSG Code). With this epsg codes, you can transform your coordinates from one projection into another.
Web Mercator -> EPSG 3857
WGS84 -> EPSG 4326
With my Code you now get a round circle in folium for Web Mercator, but be aware that it would look oval and distorted, when looking at it on a globe.
This is just a very easy explanation. You might have a look at Map Projections to better understand the problem.
This guide gives a good overview:
Map Projections
try this
folium.Circle(
radius=3000,
location=[lat,lng],
popup="Whatever name",
color="#3186cc",
fill=True,
fill_color="#3186cc",
).add_to(m)

How to do location based query with simple Django filter?

I've saved user's coordinates in the User model. Post model has latitude, longitude and radius field. Only the users in that vicinity(of Post) will be able to see that post. I don't know how to use filter() here so I used the following approach:
post=Posts.objects.all()
for a in post:
distance= geopy.distance.geodesic((lat1,lng1), (a.latitude, a.longitude)).km
print(distance)
if distance < a.radius:
p.append(a)
else:
continue
Here, lat1 and lng1 are the coordinates of current User. Suggest if there is any better way as this seems very inefficient.
Depending on your requirements, you could use a square instead of a circle. Pre-calculate the x-max, x-min, y-max and y-min boundaries for your square and then do a simple User.filter(lat__gt=lat_min, user.lng__gt=lng_min, user.lat__lt=lat_max ... lookup in the database.
In a past project, I used this:
def get_latlng_bounderies(lat, lng, distance):
"""
Return min/max lat/lng values for a distance around a latlng.
:lat:, :lng: the center of the area.
:distance: in km, the "radius" around the center point.
:returns: Two corner points of a square that countains the circle,
lat_min, lng_min, lat_max, lng_max.
"""
gc = great_circle(kilometers=distance)
p0 = gc.destination((lat, lng), 0)
p90 = gc.destination((lat, lng), 90)
p180 = gc.destination((lat, lng), 180)
p270 = gc.destination((lat, lng), 270)
ret = p180[0], p270[1], p0[0], p90[1]
return ret
Its not a circle, so its not exact around the "corners" of the square, but its much faster, because its a simple float comparision in the database.

Python regridding a 2D rotated mesh grid

I have a 2D rotated rectangular grid with longitude and latitude values with dimension [405, 555] and I can't understand how to regrid it, I want a rectangular grid with the axis "parallel" to Parallels and Meridians.
I tried to use scipy interpolation functions as: griddata or RegularGridInterpolator, but I always have problem with the old grid dimension because they are 2D and rotated, the values are not repeated and I don't know how to solve it.
Sorry I can't post my original code and data because they are proprietary and I don't know how to create a MWE.
I tried this:
import scipy.interpolate.ndgriddata as ndgriddata
import numpy as np
x = np.linspace(35.0, 42.0, 405) # my new longitude
y = np.linspace(36.0, 48.0, 555) # my new latitude
X, Y = np.meshgrid(x, y)
# grid_lon: old 2D array [405, 555] for the longitude
# grid_lat: old 2D array [405, 555] for the latitude
# data: old 2D array [405, 555] for the data
test = ndgriddata.griddata((grid_lon, grid_lat), data, (X, Y), method="linear")
but, of course I obtain the error:
ValueError: invalid shape for input data points
I know like this is complicated to answer it but if someone have an idea, please let me know.
Thanks,
Ciccio
I had just to flatten the old coordinates and the data.
ndgriddata.griddata((grid_lon.flatten(), grid_lat.flatten()),
data.flatten(), (X, Y), method="linear")

GeoDjango: Determine the area of a polygon

In my model I have a polygon field defined via
polygon = models.PolygonField(srid=4326, geography=True, null=True, blank=True)
When I want to determine the area of the polygon, I call
area_square_degrees = object.polygon.area
But how can I convert the result in square degrees into m2 with GeoDjango?
This answer does not work, since area does not have a method sq_m. Is there any built-in conversion?
You need to transform your data to the correct spatial reference system.
area_square_local_units = object.polygon.transform(srid, clone=False).area
In the UK you might use the British National Grid SRID of 27700 which uses meters.
area_square_meters = object.polygon.transform(27700, clone=False).area
You may or may not want to clone the geometry depending on whether or not you need to do anything else with it in its untransformed state.
Docs are here https://docs.djangoproject.com/en/1.8/ref/contrib/gis/geos/
I have struggled a lot with this, since i could'nt find a clean solution. The trick is you have to use the postgis capabilities (and and thus its only working with postgis..):
from django.contrib.gis.db.models.functions import Area
loc_obj = Location.objects.annotate(area_=Area("poly")).get(pk=??)
# put the primary key of the object
print(loc_obj.area_) # distance object, result should be in meters, but you can change to the unit you want, e.g .mi for miles etc..
The models.py:
class Location(models.Model):
poly = gis_models.PolygonField(srid=4326, geography=True)
It's i think the best way to do it if you have to deal with geographic coordinates instead of projections. It does handle the curve calculation of the earth, and the result is precise even with big distance/area
I needed an application to get the area of poligons around the globe and if I used an invalid country/region projection I got the error OGRException: OGR failure
I ended using an OpenLayers implementation
using the 4326 projection (is the default projection) to avoid concerning about every country/region specific projection.
Here is my code:
import math
def getCoordsM2(coordinates):
d2r = 0.017453292519943295 # Degrees to radiant
area = 0.0
for coord in range(0, len(coordinates)):
point_1 = coordinates[coord]
point_2 = coordinates[(coord + 1) % len(coordinates)]
area += ((point_2[0] - point_1[0]) * d2r) *\
(2 + math.sin(point_1[1] * d2r) + math.sin(point_2[1] * d2r))
area = area * 6378137.0 * 6378137.0 / 2.0
return math.fabs(area)
def getGeometryM2(geometry):
area = 0.0
if geometry.num_coords > 2:
# Outer ring
area += getCoordsM2(geometry.coords[0])
# Inner rings
for counter, coordinates in enumerate(geometry.coords):
if counter > 0:
area -= getCoordsM2(coordinates)
return area
Simply pass your geometry to getGeometryM2 function and you are done!
I use this function in my GeoDjango model as a property.
Hope it helps!
If Its earths surface area that you are talking about, 1 square degree has 12,365.1613 square km. So multiple your square degree and multiply by 10^6 to convert to meters.

SetViewBox moving the paper

I am using the setViewBox() function in Raphael 2. The width and height is multiplied by a value like (1.2, 1.3 ...). This changes the magnification/ zooming properly but the x and y which I have given as 0,0 makes the paper display its contents after some offset. If i modify the x and y to some positive value after the rendering( using firebug!!) then the top left of the paper moves back and above to its right position. I want to know how will the value be calculated. I have no idea about how the x,y affect the viewbox. If anybody can give me any pointers for this it will be a real help.
I have tried giving the difference between the width/ height divided by 2. Also I must mention that I am not rendering an image but various raphael shapes e.g. rects, paths text etc. in my paper.
Looking forward to some help!
Kavita
this is an example showing how to calculate the setViewBox values, I included jquery (to get my SVG cocntainer X and Y : $("#"+map_name).offset().left and $("#"+map_name).offset().top) and after that I calculated how much zoom I need :
var original_width = 777;
var original_height = 667;
var zoom_width = map_width*100/original_width/100;
var zoom_height = map_height*100/original_height/100;
if(zoom_width<=zoom_height)
zoom = zoom_width;
else
zoom = zoom_height;
rsr.setViewBox($("#"+map_name).offset().left, $("#"+map_name).offset().top, (map_width/zoom), (map_height/zoom));
did you put the center of your scaling to 0,0 like:
element.scale(1.2,1.2,0,0);
this can scale your element without moving the coordinates of the top left corner.