I have a raster file and a WGS84 lat/lon point.
I would like to know what value in the raster corresponds with the point.
My feeling is that I should use GetSpatialRef() on the raster object or one of its bands and then apply a ogr.osr.CoordinateTransformation() to the point to map it to the raster's space.
My hope would then be that I could simply ask the rasters' bands what is at that point.
However, the raster object doesn't seem to have a GetSpatialRef() or a way to access a geo-located point, so I'm somewhat at a loss for how to do this.
Any thoughts?
Say i have a geotiff file test.tif. Then followin code should look up value somewhere near the pixel. I am not that confident for the part looking up cell, and will fix there is error. This page should help, "GDAL Data Model"
Also, you may go to gis.stackexchange.com to find experts, if you haven't.
import gdal, osr
class looker(object):
"""let you look up pixel value"""
def __init__(self, tifname='test.tif'):
"""Give name of tif file (or other raster data?)"""
# open the raster and its spatial reference
self.ds = gdal.Open(tifname)
srRaster = osr.SpatialReference(self.ds.GetProjection())
# get the WGS84 spatial reference
srPoint = osr.SpatialReference()
srPoint.ImportFromEPSG(4326) # WGS84
# coordinate transformation
self.ct = osr.CoordinateTransformation(srPoint, srRaster)
# geotranformation and its inverse
gt = self.ds.GetGeoTransform()
dev = (gt[1]*gt[5] - gt[2]*gt[4])
gtinv = ( gt[0] , gt[5]/dev, -gt[2]/dev,
gt[3], -gt[4]/dev, gt[1]/dev)
self.gt = gt
self.gtinv = gtinv
# band as array
b = self.ds.GetRasterBand(1)
self.arr = b.ReadAsArray()
def lookup(self, lon, lat):
"""look up value at lon, lat"""
# get coordinate of the raster
xgeo,ygeo,zgeo = self.ct.TransformPoint(lon, lat, 0)
# convert it to pixel/line on band
u = xgeo - self.gtinv[0]
v = ygeo - self.gtinv[3]
# FIXME this int() is probably bad idea, there should be
# half cell size thing needed
xpix = int(self.gtinv[1] * u + self.gtinv[2] * v)
ylin = int(self.gtinv[4] * u + self.gtinv[5] * v)
# look the value up
return self.arr[ylin,xpix]
# test
l = looker('test.tif')
lon,lat = -100,30
print l.lookup(lon,lat)
lat,lon =28.816944, -96.993333
print l.lookup(lon,lat)
Yes, the API isn't consistent. The raster (the data source) has a GetProjection() method instead (which returns WKT).
Here is a function that does what you want (drawn from here):
def extract_point_from_raster(point, data_source, band_number=1):
"""Return floating-point value that corresponds to given point."""
# Convert point co-ordinates so that they are in same projection as raster
point_sr = point.GetSpatialReference()
raster_sr = osr.SpatialReference()
raster_sr.ImportFromWkt(data_source.GetProjection())
transform = osr.CoordinateTransformation(point_sr, raster_sr)
point.Transform(transform)
# Convert geographic co-ordinates to pixel co-ordinates
x, y = point.GetX(), point.GetY()
forward_transform = Affine.from_gdal(*data_source.GetGeoTransform())
reverse_transform = ~forward_transform
px, py = reverse_transform * (x, y)
px, py = int(px + 0.5), int(py + 0.5)
# Extract pixel value
band = data_source.GetRasterBand(band_number)
structval = band.ReadRaster(px, py, 1, 1, buf_type=gdal.GDT_Float32)
result = struct.unpack('f', structval)[0]
if result == band.GetNoDataValue():
result = float('nan')
return result
Its documentation is as follows (drawn from here):
spatial.extract_point_from_raster(point, data_source, band_number=1)
data_source is a GDAL raster, and point is an OGR point object. The
function returns the value of the pixel of the specified band of
data_source that is nearest to point.
point and data_source need not be in the same reference system, but
they must both have an appropriate spatial reference defined.
If the point does not fall in the raster, RuntimeError is raised.
project = self.ds.GetProjection()
srPoint = osr.SpatialReference(wkt=project)
done... with that, the vector file has adopted the projection from input raster file
Related
I have quite complex problem and I have two options to solve it.
For a multiline shapefile (river) I would like to get cross profiles and extract DEM values for the lines.
I was thinking 1: Create ortogonal lines at defined step:
#Define a shp for the output features. Add a new field called 'M100' where the z-value of the line is stored to uniquely identify each profile
layerOut = outShp.CreateLayer('line_utm_neu_perp', layerRef, osgeo.ogr.wkbLineString)
layerDefn = layerOut.GetLayerDefn() # gets parameters of the current shapefile
layerOut.CreateField(ogr.FieldDefn('M100', ogr.OFTReal))
# Calculate the number of profiles/perpendicular lines to generate
n_prof = int(geomIn.Length()/spc)
# Define rotation vectors
rot_anti = np.array([[0, -1], [1, 0]])
rot_clock = np.array([[0, 1], [-1, 0]])
# Start iterating along the line
for prof in range(1, n_prof):
# Get the start, mid and end points for this segment
seg_st = geomIn.GetPoint(prof-1) # (x, y, z)
seg_mid = geomIn.GetPoint(prof)
seg_end = geomIn.GetPoint(prof+1)
# Get a displacement vector for this segment
vec = np.array([[seg_end[0] - seg_st[0],], [seg_end[1] - seg_st[1],]])
# Rotate the vector 90 deg clockwise and 90 deg counter clockwise
vec_anti = np.dot(rot_anti, vec)
vec_clock = np.dot(rot_clock, vec)
# Normalise the perpendicular vectors
len_anti = ((vec_anti**2).sum())**0.5
vec_anti = vec_anti/len_anti
len_clock = ((vec_clock**2).sum())**0.5
vec_clock = vec_clock/len_clock
# Scale them up to the profile length
vec_anti = vec_anti*sect_len
vec_clock = vec_clock*sect_len
# Calculate displacements from midpoint
prof_st = (seg_mid[0] + float(vec_anti[0]), seg_mid[1] + float(vec_anti[1]))
prof_end = (seg_mid[0] + float(vec_clock[0]), seg_mid[1] + float(vec_clock[1]))
# Write to output
geomLine = ogr.Geometry(ogr.wkbLineString)
geomLine.AddPoint(prof_st[0],prof_st[1])
geomLine.AddPoint(prof_end[0],prof_end[1])
featureLine = ogr.Feature(layerDefn)
featureLine.SetGeometry(geomLine)
featureLine.SetFID(prof)
featureLine.SetField('M100',round(seg_mid[2],1))
layerOut.CreateFeature(featureLine)
Problem here is that it works on one line only and not on multiline.
2 option could be creating parallel lines with offset and extract values at the same distance from the start. But I tried it only once and it did not work on my objects.
z = shapefile.offset_curve(10.0,'left')
But here I do not know what object to pass in order to make it work. Also I was thinking about creating buffer and extracting values of raster.
I will be grateful for any suggestions.
I am reading UTM point data from a shape file. The geopandas CRS string associated with the shape file is:
PROJCS["WGS 84 / Falk",GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0],UNIT["Degree",0.0174532925199433]],PROJECTION["Transverse_Mercator"],PARAMETER["latitude_of_origin",0],PARAMETER["central_meridian",-60],PARAMETER["scale_factor",0.9996],PARAMETER["false_easting",500000],PARAMETER["false_northing",10000000],UNIT["metre",1,AUTHORITY["EPSG","9001"]],AXIS["Easting",EAST],AXIS["Northing",NORTH]]
I convert the point data to lat/long using pyproj:
projn = pyproj.Proj('+proj=utm +zone=21 +south +ellps=WGS84 +datum=WGS84 +units=m +no_defs +lon_0=-60')
ylon, ylat = projn(utm_e, utm_n, inverse=True)
This gives me the correct latitude but the longitude is exactly 3 degrees out. I changed the UTM zone to utm=+20 but now am 3 degress out in the other direction. I also tried setting the false easting with x_0=500000 and the central longitude with lon_0=-60 but that made no difference. Finally, I tried setting a projection system using one of the EPSG settings in the CRS string eg
projn = pyproj.CRS.from_epsg(6326)
but that gave the error message CRSError: Invalid projection: epsg:6326: (Internal Proj Error: proj_create: crs not found). Would appreciate any suggestions as I am new to GIS and finding it difficult to understand projections. An illustration of the problem is shown below:
import pyproj
# Define points to process
well_dict = {}
well_dict['14/09-1'] = [-59.384869, -49.319319, 544706.1998681703, 4536872.299629836]
well_dict['14/09-2'] = [-59.349633, -49.247411, 547331.1995800878, 4544831.399531693]
well_dict['14/10-1'] = [-59.176736, -49.275033, 559882.9998544991, 4541663.299837932]
well_dict['14/13-1'] = [-59.496483, -49.417692, 536519.4998223917, 4525987.899903225]
well_dict['14/05-1A'] = [-59.177950, -49.162275, 559930.8995227005, 4554179.299983081]
well_dict['14/24-1'] = [-59.297611, -49.812692, 550533.2498328319, 4481958.129964017]
# Define projections
projns = {}
projns['base'] = pyproj.Proj('+proj=utm +zone=21 +south +ellps=WGS84 +datum=WGS84 +units=m +no_defs +lon_0=-60')
projns['6326'] = pyproj.CRS.from_epsg(6326) #FIXME: gives CRSerror
# projns['6326'] = pyproj.Proj('epsg:6326')
# projns['7030'] = pyproj.Proj('epsg:7030')
# projns['9001'] = pyproj.Proj('epsg:9001')
# Convert UTMs for each well to lat/long using each projection
for well in well_dict:
xlon, xlat, utm_e, utm_n = well_dict[well]
print("%-9s %10.2f %10.2f %7.3f %7.3f" % (well, utm_e, utm_n, xlat, xlon), end='')
for pname in projns:
projn = projns[pname]
ylon, ylat = projn(utm_e, utm_n, inverse=True)
print(" %7.3f %7.3f" % (ylat, ylon), end='')
print("")
EDIT:
After further investigation I found that I should have been using the Transverse Mercator projection, not Universal Transverse Mercator. If I use:
projns['tmerc'] = pyproj.Proj('+proj=tmerc +lat_0=0 +lon_0=-60 +k=0.9996 +x_0=500000 +y_0=10000000 +datum=WGS84 +units=m +no_defs')
then the points plot in the correct position.
Recommendations:
Use Transformer (https://pyproj4.github.io/pyproj/stable/gotchas.html#upgrading-to-pyproj-2-from-pyproj-1)
The WKT can be used as input directly
import pyproj
wkt = 'PROJCS["WGS 84 / Falk",GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0],UNIT["Degree",0.0174532925199433]],PROJECTION["Transverse_Mercator"],PARAMETER["latitude_of_origin",0],PARAMETER["central_meridian",-60],PARAMETER["scale_factor",0.9996],PARAMETER["false_easting",500000],PARAMETER["false_northing",10000000],UNIT["metre",1,AUTHORITY["EPSG","9001"]],AXIS["Easting",EAST],AXIS["Northing",NORTH]]'
transformer = pyproj.Transformer.from_crs(wkt, "EPSG:4326", always_xy=True)
# Define points to process
well_dict = {}
well_dict['14/09-1'] = [-59.384869, -49.319319, 544706.1998681703, 4536872.299629836]
well_dict['14/09-2'] = [-59.349633, -49.247411, 547331.1995800878, 4544831.399531693]
well_dict['14/10-1'] = [-59.176736, -49.275033, 559882.9998544991, 4541663.299837932]
well_dict['14/13-1'] = [-59.496483, -49.417692, 536519.4998223917, 4525987.899903225]
well_dict['14/05-1A'] = [-59.177950, -49.162275, 559930.8995227005, 4554179.299983081]
well_dict['14/24-1'] = [-59.297611, -49.812692, 550533.2498328319, 4481958.129964017]
# Define projections
# Convert UTMs for each well to lat/long using each projection
for well in well_dict:
xlon, xlat, utm_e, utm_n = well_dict[well]
print("%-9s %10.2f %10.2f %7.3f %7.3f" % (well, utm_e, utm_n, xlat, xlon), end='')
ylon, ylat = transformer.transform(utm_e, utm_n)
print(" %7.3f %7.3f" % (ylat, ylon))
Output:
14/09-1 544706.20 4536872.30 -49.319 -59.385 -49.319 -59.385
14/09-2 547331.20 4544831.40 -49.247 -59.350 -49.247 -59.350
14/10-1 559883.00 4541663.30 -49.275 -59.177 -49.275 -59.177
14/13-1 536519.50 4525987.90 -49.418 -59.496 -49.418 -59.496
14/05-1A 559930.90 4554179.30 -49.162 -59.178 -49.162 -59.178
14/24-1 550533.25 4481958.13 -49.813 -59.298 -49.813 -59.298
I have (lat,long) coordinate describing the position of a point in a .geotiff image.
I wish to find the equivalent pixel coordinates of the lat,long ones inside the image.
I succeded using gdaltransform from the command line with the following instruction :
gdaltransform -i -t_srs epsg:4326 /path/imagename.tiff
-17.4380493164062 14.6951949085676
But i would like to retrieve such type of equivalence from python code. I tried the following :
from osgeo import osr
source = osr.SpatialReference()
source.ImportFromUrl(path + TIFFFilename)
target = osr.SpatialReference()
target.ImportFromEPSG(4326)
transform = osr.CoordinateTransformation(target,source )
point_xy = np.array(transform.TransformPoint(-17.4380493164062,14.6951949085676))
But it returns this error :
NotImplementedError: Wrong number or type of arguments for overloaded function 'CoordinateTransformation_TransformPoint'.
Possible C/C++ prototypes are:
OSRCoordinateTransformationShadow::TransformPoint(double [3])
OSRCoordinateTransformationShadow::TransformPoint(double [3],double,double,double)
What am i doing wrong ? I tried to work around this error but without any success. Is there an other way to do it ?
EDIT 1 :
I achieved a single transformation via gdaltransform commands in terminal :
gdaltransform -i -t_srs epsg:4326 /path/image.tiff
-17.4380493164062 14.6951949085676
As i need to retrieve the pixel in a pythonic way, i tried calling the command using subprocess like :
# TRY 1:
subprocess.run(['gdaltransform','-i',' -t_srs','epsg:4326','/pat/img.tiff\n'], stdout=subprocess.PIPE)
# TRY 2 :
cmd = '''gdaltransform -i -t_srs epsg:4326 /home/henri/Work/imdex_visio/AllInt/Dakar_X118374-118393_Y120252-120271_PHR1A_2016-03-10T11_45_39.781Z_Z18_3857.tiff
-17.4380493164062 14.6951949085676'''
subprocess.Popen(cmd,stdout=subprocess.PIPE, shell=True)
But it does not work. Maybe because of the way the command itself behaves, like not actually returning a result and ending itself, but displaying the result and staying busy.
According to the cookbook you are flipping the use of transform and point. You should call transform on the point given the transform, not the other way around. It also seems like you are flipping source and target, but you do that two times, so it will work.
However I believe that target.ImportFromUrl(path + TIFFFilename) will not work. Instead you can extract the spatial reference from the geotiff using gdal.
Something like the following should work
from osgeo import osr, ogr, gdal
# Extract target reference from the tiff file
ds = gdal.Open(path + TIFFFilename)
target = osr.SpatialReference(wkt=ds.GetProjection())
source = osr.SpatialReference()
source.ImportFromEPSG(4326)
transform = osr.CoordinateTransformation(source, target)
point = ogr.Geometry(ogr.wkbPoint)
point.AddPoint(-17.4380493164062, 14.6951949085676)
point.Transform(transform)
print(point.GetX(), point.GetY())
This provides you with the coordinates in your geotiffs reference, however this is not pixel coordinates.
To convert the point to pixels you could use something like the following (the minus for the line might have to be flipped, based on where in the world you are)
def world_to_pixel(geo_matrix, x, y):
"""
Uses a gdal geomatrix (gdal.GetGeoTransform()) to calculate
the pixel location of a geospatial coordinate
"""
ul_x= geo_matrix[0]
ul_y = geo_matrix[3]
x_dist = geo_matrix[1]
y_dist = geo_matrix[5]
pixel = int((x - ul_x) / x_dist)
line = -int((ul_y - y) / y_dist)
return pixel, line
So your final code would look something like
from osgeo import osr, ogr, gdal
def world_to_pixel(geo_matrix, x, y):
"""
Uses a gdal geomatrix (gdal.GetGeoTransform()) to calculate
the pixel location of a geospatial coordinate
"""
ul_x= geo_matrix[0]
ul_y = geo_matrix[3]
x_dist = geo_matrix[1]
y_dist = geo_matrix[5]
pixel = int((x - ul_x) / x_dist)
line = -int((ul_y - y) / y_dist)
return pixel, line
# Extract target reference from the tiff file
ds = gdal.Open(path + TIFFFilename)
target = osr.SpatialReference(wkt=ds.GetProjection())
source = osr.SpatialReference()
source.ImportFromEPSG(4326)
transform = osr.CoordinateTransformation(source, target)
point = ogr.Geometry(ogr.wkbPoint)
point.AddPoint(-17.4380493164062, 14.6951949085676)
point.Transform(transform)
x, y = world_to_pixel(ds.GetGeoTransform(), point.GetX(), point.GetY())
print(x, y)
The proposed solution might work in most of the cases as row/column rotation is typically zero, but it should be at least checked or better included:
import numpy as np
from osgeo import gdal
def world_to_pxl(gt, x, y):
# 'Affine transformation': W = A * pxl + ul
# world[2, 1] = a[2, 2] * pxl[2, 1] + upper_left[2,1]
world = np.array([[x], [y]]) # world coordinates
upper_left = np.array(
[
[gt[0]], [gt[3]] # upper left corner of image
]
)
a = np.array([[gt[1], gt[2]],
[gt[4], gt[5]]])
# Reformulate: A^-1 * (W - ul) = pxl
pxl = np.matmul(np.linalg.inv(a), (world - upper_left))
row = pxl[0] # x_pixel
col = pxl[1] # y_line
return row, col
I want to use a raster for a A* and bidirectional Dijkstra path analysis in NetworkX. I am using Python for this project.
Raster example (it's a png file converted when uploaded, but the real problem is TIFF):
First I read in the raster with GDAL
input_raster = "raster.tif"
raster = gdal.Open(input_raster)
Next I read the raster as an array
bandraster = raster.GetRasterBand(1)
arr = bandraster.ReadAsArray()
So, I'll transform coords using a function:
def coord2pixelOffset(rasterfn, x, y):
raster = gdal.Open(rasterfn)
geotransform = raster.GetGeoTransform()
originX = geotransform[0]
originY = geotransform[3]
pixelWidth = geotransform[1]
pixelHeight = geotransform[5]
xOffset = int((x - originX)/pixelWidth)
yOffset = int((y - originY)/pixelHeight)
return xOffset, yOffset
CostSurfacefn = 'raster.tif'
source_coord = (-41.1823753163, -13.83393276)
target_coord = (-40.3726182077, -14.2361991946)
# coordinates to array index
source = coord2pixelOffset(CostSurfacefn, source_coord[0], source_coord[1])
target = coord2pixelOffset(CostSurfacefn, target_coord[0], target_coord[1])
The array is like this (example):
# Grid with 2x2. The float numbers are the pixel values
[[ 1.83781120e+08 1.90789248e+08]
[ 1.83781120e+08 1.90789248e+08]]
# array[0][0] is 1.83781120e+08
# array[0][1] is 1.90789248e+08
# array[1][0] is 1.83781120e+08
# array[1][1] is 1.90789248e+08
Next, the graph is loaded and bi-dijkstra function is called (but I want for example from array[0][0] to array[1][1] ):
G = nx.from_numpy_matrix(np.array(arr))
length, path = nx.bidirectional_dijkstra(G, source, target)
How to get the node id of source and target by array?
I wrote a custom exporter that dumps a Blender mesh to a simple binary format. I can read extremely simple models like a cube from files exported by my script but more complex models like the monkey included with Blender do not work. Instead, complex models have many of the vertices connected wrongly. I believe that when I am looping through the vertex indices in my script I am not doing so in the right order. How can I reorder indices pointing to vertices in a Blender Python export script so that my vertices will be connected correctly? Below is the exporter script (with comments that explain the file format.)
import struct
import bpy
def to_index(number):
return struct.pack(">I", number)
def to_GLfloat(number):
return struct.pack(">f", number)
# Output file structure
# A file is a single mesh
#
# A mesh is a list of vertices, normals, and indices
#
# index number_of_triangles
# triangle triangles[number_of_triangles]
# index number_of_vertices
# vertex vertices[number_of_vertices]
# normal vertices[number_of_vertices]
#
# A triangles is a 3-tuple of indices pointing to vertices in the corresponding vertex list
#
# index vertices[3]
#
# A vertex is a 3-tuple of GLfloats
#
# GLfloat coordinates[3]
#
# A normal is a 3-tuple of GLfloats
#
# GLfloat normal[3]
#
# A GLfloat is a big endian 4 byte floating point IEEE 754 binary number
# An index is a big endian unsigned 4 byte binary number
def write_kmb_file(context, filepath):
meshes = bpy.data.meshes
if 1 != len(meshes):
raise Exception("Expected a single mesh")
mesh = meshes[0]
faces = mesh.polygons
vertex_list = mesh.vertices
output = to_index(len(faces))
for face in faces:
vertices = face.vertices
if len(vertices) != 3:
raise Exception("Only triangles were expected")
output += to_index(vertices[0])
output += to_index(vertices[1])
output += to_index(vertices[2])
output += to_index(len(vertex_list))
for vertex in vertex_list:
x, y, z = vertex.co.to_tuple()
output += to_GLfloat(x)
output += to_GLfloat(y)
output += to_GLfloat(z)
for vertex in vertex_list:
x, y, z, = vertex.normal.to_tuple()
output += to_GLfloat(x)
output += to_GLfloat(y)
output += to_GLfloat(z)
out = open(filepath, 'wb')
out.write(output)
out.close()
return {'FINISHED'}
from bpy_extras.io_utils import ExportHelper
from bpy.props import StringProperty
from bpy.types import Operator
class ExportKludgyMess(Operator, ExportHelper):
bl_idname = "mesh.export_to_kmb"
bl_label = "Export KMB"
filename_ext = ".kmb"
filter_glob = StringProperty(
default="*.kmb",
options={'HIDDEN'},
)
def execute(self, context):
return write_kmb_file(context, self.filepath)
def register():
bpy.utils.register_class(ExportKludgyMess)
if __name__ == "__main__":
register()
Your most likely just dumping the verts. Since each vert is used in multiple faces file formats like obj indicates verts with 'v', vert normals with 'vn' and UVs with 'vt'. However this is NOT the draw order. The 'f' prefix indicates with order of verts for each face. For
example f 1/1/1 2/4/3 3/4/5 use
verts 1,2,3
normals 1,4,4
UV 1,3,5
I know its old but hey.