Assigning a 3d polygon based on value in Mapbox Studio - mapbox

I am pretty new to GIS as a whole. I have a simple flat file in a csv format, as an example:
name, detail, long, lat, value
a, 123, 103, 22, 5000
b, 356, 103, 45, 6000
What I am trying to achieve is to assign a 3d polygon in Mapbox such as in this example. While the settings might be quite straight forward in Mapbox where you assign a height and color value based on a data range, it obviously does not work in my case.
I think I am missing out other files such as mentioned in the blog post, like shapefiles or some other file that is required to assign 3d layouts to the 3d extrusion.
I need to know what I am missing out in configuring a 3d polygon, say a cube in Mapbox based on the val data column in my csv.

So I figured what I was missing was the coordinates that make up the polygons I want to display. This can easily be defined in a geojson file format, if you are interested in the standards, refer here. For the visual I need, I would require:
Points (typically your long and lat coordinates)
Polygon (a square would require 5 vertices, the lines connecting and
defining your polygon)
Features (your data points)
FeatureCollection (a collection of features)
This are all parts of the geojson format, I used Python and its geojson module which comes with everything I need to do the job.
Using a helper function below, I am able to compute square/rectangular boundaries based on a single point. The height and width defines how big the square/rectangle appears.
def create_rec(pnt, width = 0.00005, height = 0.00005):
pt1 = (pnt[0] - width, pnt[1] - height)
pt2 = (pnt[0] - width, pnt[1] + height)
pt3 = (pnt[0] + width, pnt[1] + height)
pt4 = (pnt[0] + width, pnt[1] - height)
pt5 = (pnt[0] - width, pnt[1] - height)
return Polygon([[pt1,pt2,pt3,pt4,pt5]]) #assign to a Polygon class from geojson
From there it is pretty straight forward to append them into list of features, FeatureCollection and output as a geojson file:
with open('path/coordinates.csv', 'r') as f:
headers = next(f)
reader = csv.reader(f)
data = list(reader)
transform = []
for i in data:
#3rd last value is x and 2nd last is the y
point = Point([float(i[-3]), float(i[-2])])
polygon = create_rec(point['coordinates'])
#in my case I used a collection to store both points and polygons
col = GeometryCollection([point, polygon])
properties = {'Name':i[0]}
feature = Feature(geometry = col, properties = properties)
transform.append(feature)
fc = FeatureCollection(transform)
with open('target_doc_u.geojson', 'w') as f:
dump(fc, f)
The output file target_doc_u would contain all the listed items above that allows me to plot my point, as well as continue of the blog post in Mapbox to assign my filled extrusion

Related

Find pixel coordinate of world/geographic coordinate in tile

I'm trying to use Mapbox Terrain RGB to get elevation for specific points in space. I used mercantile.tile to get the coordinates of the tile containing my point at zoom level 15, which for -43º, -22º (for simplicity sake) is 12454, 18527, then mercantile.xy to get the corresponding world coordinates: -4806237.7150042495, -2621281.2257876047.
Shouldn't the integer part of -4806237.7150042495 / 256 (tile size) equal the x coordinate of the tile containing the point, that is, 12454? If this calculation checked out I'd figure that I'm looking for the pixel column (x axis) corresponding to the decimal part of the result, like column 127(256 * 0,5) for 12454,5. However, the division results in -18774.366, (which is curiously close to the tile y coordinate, but it looks like a coincidence). What am I missing here?
As an alternative, I thought of using mercantile.bounds, assigning the first and last pixel columns to the westmost and eastmost longitudes, and finding my position with interpolation, but I wanted to check if I'm doing this the right/recommended way. I'm interested in point elevations, so everything said here goes for the Y axis as well.
Here's what I got so far:
def correct_altitude_mode(kml):
with open(kml, "r+") as f:
txt = f.read()
if re.search("(?<=<altitudeMode>)relative(?=<\/altitudeMode>)", txt):
lat = round(float(find_with_re("latitude", txt)), 5)
lng = round(float(find_with_re("longitude", txt)), 5)
alt = round(float(find_with_re("altitude", txt)), 5)
z = 15
tile = mercantile.tile(lng, lat, z)
westmost, southmost, eastmost, northmost = mercantile.bounds(tile)
pixel_column = np.interp(lng, [westmost, eastmost], [0,256])
pixel_row = np.interp(lat, [southmost, northmost], [256, 0])
response = requests.get(f"https://api.mapbox.com/v4/mapbox.terrain-rgb/{z}/{tile.x}/{tile.y}.pngraw?access_token=pk.eyJ1IjoibWFydGltcGFzc29zIiwiYSI6ImNra3pmN2QxajBiYWUycW55N3E1dG1tcTEifQ.JFKSI85oP7M2gbeUTaUfQQ")
buffer = BytesIO(response.content)
tile_img = png.read_png_int(buffer)
_,R,G,B = (tile_img[int(pixel_row), int(pixel_column)])
print(tile_img[int(pixel_row), int(pixel_column)])
height = -10000 + ((R * 256 * 256 + G * 256 + B) * 0.1)
print(f"R:{R},G:{G},B:{B}\n{height}")
plt.hlines(pixel_row, 0.0, 256.0, colors="r")
plt.vlines(pixel_column, 0.0, 256.0, colors="r")
plt.imshow(tile_img)

Converting from WGS84 to EPSG:27700 raster tiles without drawing a map

Using this example from OS Data Hub - https://labs.os.uk/public/os-data-hub-examples/os-maps-api/zxy-27700-basic-map
I can get a list of tiles displayed on the map, I would like to get the coordinates of the tile without drawing the map.
Starting from a single point in WGS84 (lat/long) I can convert this to EPSG:27700 using Proj4js
var source = new proj4.Proj('EPSG:4326');
proj4.defs("EPSG:27700","+proj=tmerc +lat_0=49 +lon_0=-2 +k=0.9996012717 +x_0=400000 +y_0=-100000 +ellps=airy +datum=OSGB36 +units=m +no_defs");
var dest = new proj4.Proj('EPSG:27700');
var coords=proj4.transform(source, dest, [X,Y]);
I then need to translate this into coordinates for the raster tile, which is done in the leaflet example with this code:
var crs = new L.Proj.CRS('EPSG:27700', '+proj=tmerc +lat_0=49 +lon_0=-2 +k=0.9996012717 +x_0=400000 +y_0=-100000 +ellps=airy +towgs84=446.448,-125.157,542.06,0.15,0.247,0.842,-20.489 +units=m +no_defs', {
resolutions: [ 896.0, 448.0, 224.0, 112.0, 56.0, 28.0, 14.0, 7.0, 3.5, 1.75 ],
origin: [ -238375.0, 1376256.0 ]
});
How can i replicate this step to produce the tile coordinates, without having to draw the leaflet map?
I ultimately want to use the coordinates to grab & save a single tile from the OS Data Hub with this format:
https://api.os.uk/maps/raster/v1/zxy/layer/%7Bz%7D/%7Bx%7D/%7By%7D.png?key=
Using the EPSG:27700 coords calculated using proj4, and the zoom level resolutions (which are meters per pixel) and tile grid origin coordinates used in the definition you can calculate the {x} and {y} values in https://api.os.uk/maps/raster/v1/zxy/layer/{z}/{x}/{y}.png?key= for any zoom level {z} based on the standard tile size of 256 pixels as
x = Math.floor((coords[0] - origin[0]) / (resolutions[z] * 256));
y = Math.floor((origin[1] - coords[1]) / (resolutions[z] * 256));

Can map.getBounds be executed for a different coordinate system?

I execute the following code in my leaflet webmap
map.getBounds().getWest() + "&y1=" +
map.getBounds().getSouth() + "&x2=" +
map.getBounds().getEast() + "&y2=" +
map.getBounds().getNorth()
This results in a result showing me four coordinates in the WGS84 (standard) coordinate system.
Is there any way to alter this so it will output 28992 coordinates instead?
I guess that by "28992 coordinates" you're referring to the EPSG:28992 Coordinate Reference System.
The canonical way to use "non-standard" CRSs in Leaflet is to leverage proj4leaflet. This answer assumes that you're already doing so.
So the getBounds() method of L.Map always returns a L.LatLngBounds instance, which refer to unprojected WGS84 coordinates. However, we can use the map's CRS to project a L.LatLng into a L.Point with the projected coordinates, in the map's display CRS; e.g.
var map = L.map('containerId`, { crs: crsForEpsg28992 });
var foo = map.options.crs.project(L.latLng([60.3,21.1]));
var qux = map.options.crs.project(map.getCenter());
Because of how map projections work (they rotate and bend the coordinate spaces), and because of how proj4js is implemented, it's not possible to project a bounding box into a bounding box. (In most cases, the projection of a bounding box would be a curved polygon!). This image from an article by Gregor Aisch illustrates the issue:
We can, however, do an approximation: project the four corners of the bounding box, e.g.:
var mapBounds = map.getBounds();
var crs = map.options.crs;
var nw = crs.project(mapBounds.getNorthWest());
var ne = crs.project(mapBounds.getNorthEast());
var sw = crs.project(mapBounds.getSouthWest());
var se = crs.project(mapBounds.getSouthEast());
We can even create a L.Bounds (but not a L.LatLngBounds!) from those projected coordinates; that'll be a bbox in the specified CRS that contains all corners, e.g.:
var bbox = L.bounds([nw, ne, sw, se]);
It's not gonna be perfect, but that approximation should be enough for most use cases.
See also this working example (based off on one of the proj4leaflet examples), which should further illustrate the issue.

Displaying georeferenced images using OpenLayers 5

I'm trying to make an application where the user can georeference scanned maps. You can look at an example here: https://codesandbox.io/s/2o99jvrnyy
There are two images:
assets/test.png - without rotation
assets/test_rotation.png - with rotation
The first image is loaded correctly on the map but the one with rotation is not.
I can't find information on whether OpenLayers 5 can handle images with transformation parameters stored in world file. Probably I'm missing something but can't figure out what.
This is how my logic works:
Transformation parameters are calculated with affine transformation using 4 points. You can see the logic in Affine.js file. At least 4 points are picked up from the source image and the map. Then using these 4 points the transformation parameters are calculated. After that I'm calculating the extent of the image:
width = image.width in pixels
height = image.height in pixels
width *= Math.sqrt(Math.pow(parameters.A, 2) + Math.pow(parameters.D, 2));
height *= Math.sqrt(Math.pow(parameters.B, 2) + Math.pow(parameters.E, 2));
// then the extent in projection units is
extent = [parameters.C, parameters.F - height, parameters.C + width, parameters.F];
World file parameters are calculated as defined here.
Probably the problem is that the image with rotation is not rotated when loaded as static image in OpenLayers 5, but can't find a way to do it.
I tried to load both images in QGIS and ArcMap with calculated parameters and both of them are loaded correctly. You can see the result for the second picture:
You can see the parameters for each image here:
Image: test.png
Calculated extent: [436296.79726721847, 4666723.973240128, 439864.3389057907, 4669253.416495154]
Calculated parameters (for world file):
3.8359372067274027
-0.03146800786355865
-0.03350636818089405
-3.820764346376064
436296.79726721847
4669253.416495154
Image: test_rotation.png
Calculated extent: [437178.8291026594, 4667129.767589236, 440486.91675884253, 4669768.939256327]
Calculated parameters (for world file):
3.506332904308879
-1.2831186688536016
-1.3644002712982917
-3.7014921022625864
437178.8291026594
4669768.939256327
I realized that my approach was wrong. There is no need to calculate the extent of the image in map projection and set it in the layer. I can simply add a transformation function responsible for transforming coordinates between image projection and map projection. This way the image layer has always it's projection set to image projection and extent set to the size of the image in pixels.
The transformation function is added like this:
import { addCoordinateTransforms } from 'ol/proj.js';
addCoordinateTransforms(
mapProjection,
imageProjection,
coords => {
// forward
return Affine.transform(coords);
},
coords => {
// inverse
}
)
Affine parameters are again calculated from at least 3 points:
// mapPoints - coordinates in map projection
// imagePoints - coordinates in image projection
Affine.calculate(mapPoints, imagePoints);
You can see a complete example here - https://kw9l85y5po.codesandbox.io/

How to get depth value of specific RGB pixels in Kinect v2 images using Matlab

I'm working with the Kinect v2 and I have to map the depth information onto the RGB images to process them: in particular, I need to know which pixels in the RGB images are in a certain range of distance (depth) along the Z axis; I'm acquiring all the data with a C# program and saving them as images (RGB) and txt files (depth).
I've followed the instruction from here and here (and I thank them for sharing), but I still have some problems I don't know how to solve.
I have calculated the rotation (R) and translation (T) matrix between the depth sensor and the RGB camera, as well as their intrinsic parameters.
I have created P3D_d (depth pixels in world coordinates related to depth sensor) and P3D_rgb (depth pixels in world coordinates related to rgb camera).
row_num = 424;
col_num = 512;
P3D_d = zeros(row_num,col_num,3);
P3D_rgb = zeros(row_num,col_num,3);
cont = 1;
for row=1:row_num
for col=1:col_num
P3D_d(row,col,1) = (row - cx_d) * depth(row,col) / fx_d;
P3D_d(row,col,2) = (col - cy_d) * depth(row,col) / fy_d;
P3D_d(row,col,3) = depth(row,col);
temp = [P3D_d(row,col,1);P3D_d(row,col,2);P3D_d(row,col,3)];
P3D_rgb(row,col,:) = R*temp+T;
end
end
I have created P2D_rgb_x and P2D_rgb_y.
P2D_rgb_x(:,:,1) = (P3D_rgb(:,:,1)./P3D_rgb(:,:,3))*fx_rgb+cx_rgb;
P2D_rgb_y(:,:,2) = (P3D_rgb(:,:,2)./P3D_rgb(:,:,3))*fy_rgb+cy_rgb;
but now I don't understand how to continue.
Assuming that the calibration parameters are correct, I've tried to click on a defined point in both the depth (coordinates: row_d, col_d) and rgb (coordinates: row_rgb, col_rgb) images, but P2D_rgb_x(row_d, col_d) is totally different from row_rgb, as well as P2D_rgb_y(row_d, col_d) is totally different from col_rgb.
So, what do exactly mean P2D_rgb_x and P2D_rgb_y? How can I use them to map depth value onto rgb images or just to get the depth of a certain RGB pixel?
I'll apreciate any suggest or help!
PS: I've also a related post on MathWorks at this link