Problem
I have a screenshot of a section of a map (like a tile) in a numpy array.
- The projection of the map is UTM.
- I know the width and height in pixels of the image.
- I do not know the total pixel width and height of the assembled tiles for the whole world.
- I also know the corresponding lat/longs for each corner of the tile, as well as 10 (I can get more if necessary) GCPs throughout the image.
- The image is always north up with no rotation.
I need a python function that accepts my gcp list, the image, and an arbitrary pixel x,y and returns the longitude and latitude for the x,y pixel pair. I have found many related questions but none seem to get me there. I am trying to use gdal but am open to any modern python library since I have to do this many times.
Attempted Solution
Here is what I have so far:
- Make numpy array gdal friendly:
def array2Gtiff(sct, array):
cols = array.shape[1]
rows = array.shape[0]
outRaster = driver.Create('test.tiff', cols, rows, 1, gdal.GDT_Byte)
outband = outRaster.GetRasterBand(1)
outband.WriteArray(array[:,:,0])
outRasterSRS = osr.SpatialReference()
outRasterSRS.ImportFromEPSG(3395)
references = getReferences(sct)
gcps = []
for r in references:
lat = r[0]
y = r[1]
lon = r[2]
x = r[3]
gcps.append(gdal.GCP(float(lon), float(lat), 0, float(x), float(y)))
outRaster.SetGCPs(gcps, outRasterSRS.ExportToWkt())
error_threshold = 0.125
resampling=gdal.GRA_NearestNeighbour
tmp_ds = gdal.AutoCreateWarpedVRT( outRaster,
None,
outRasterSRS.ExportToWkt(),
resampling,
error_threshold)
return (outputRaster, tmp_ds)
- Try to get long and lat:
def getPositionOfSymbolUsingGDAL(rasters, symbol):
old = rasters[0].GetProjectionRef()
new = rasters[1].GetProjectionRef()
x = symbol[0]
y = symbol[1]
t = osr.CoordinateTransformation(old, new)
lat, lon, z = t.TransformPoint(x, y)
return (lon, lat)
Edit
Okay attempt 2
def array2Gtiff(sct, array):
cols = array.shape[1]
rows = array.shape[0]
driver = gdal.GetDriverByName('MEM')
outRaster = driver.Create('test.tiff', cols, rows, 1, gdal.GDT_Byte)
outband = outRaster.GetRasterBand(1)
outband.WriteArray(array[:,:,0])
outRasterSRS = osr.SpatialReference()
outRasterSRS.ImportFromEPSG(4326)
references = getReferences(sct)
gcps = []
for r in references:
lat = r[0]
y = r[1]
lon = r[2]
x = r[3]
gcps.append(gdal.GCP(float(lon), float(lat), 0, float(x), float(y)))
outRaster.SetGCPs(gcps, outRasterSRS.ExportToWkt())
error_threshold = 0.01
resampling=gdal.GRA_NearestNeighbour
tmp_ds = gdal.AutoCreateWarpedVRT( outRaster,
None,
outRasterSRS.ExportToWkt(),
resampling,
error_threshold)
outRaster.SetProjection(outRasterSRS.ExportToWkt())
outband.FlushCache()
return (outRaster,tmp_ds)
def getPositionOfSymbolUsingGDAL(rasters, symbol):
old = rasters[0].GetProjectionRef()
new = rasters[1].GetProjectionRef()
xoffset, px_w, rot1, yoffset, rot2, px_h = rasters[1].GetGeoTransform()
x = symbol[0]
y = symbol[1]
posX = px_w * x + rot1 * y + xoffset
posY = rot2 * x + px_h * y + yoffset
old_cs= osr.SpatialReference()
old_cs.ImportFromWkt(old)
new_cs= osr.SpatialReference()
new_cs.ImportFromWkt(new)
t = osr.CoordinateTransformation(old_cs, new_cs)
lat, lon, z = t.TransformPoint(posX, posY)
return (lon, lat)
Ok getting super close. This gives lons and lats in the right ballpark but off by 2 or 3 degrees around lat 50 and lon -16. Any other pointers?
Edit 2:
I am closer than ever. Field of view is roughly 7 degrees of longitude (at latitude 52), roughly 1 degree of latitude.
This is the expected list of lons and lats from another but much slower method that I have of obtaining the lon and lat from xy: TRUE LIST: (52.828163, -21.983257) (53.121275, -23.904298) (53.307069, -22.928915) (53.441183, -21.808616) (53.488742, -25.567096) (53.699348, -24.666027) (53.735629, -23.664633) (53.782858, -22.170902) (53.872774, -25.522507)
CALCULATED WITH GDAL: (52.825434609375, -22.060532004780878) (53.115689671874996, -23.943306352988046) (53.300699578125, -22.98735225936255) (53.434748671875, -21.889370700398405) (53.482385953125, -25.57298047450199) (53.693984109375, -24.689860978486056) (53.730542953124996, -23.70841477569721) (53.778180234375, -22.24443936374502) (53.869023421875, -25.529279715936255)
Updated Code:
def array2Gtiff(sct, array):
# tl and br in (lat, lon)
tl = getTopLeftPostition(sct)
br = getBottomRightPosition(sct)
deltaX = abs(tl[1] - br[1])
deltaY = abs(tl[0] - br[0])
cols = array.shape[1]
rows = array.shape[0]
driver = gdal.GetDriverByName('MEM')
old = driver.Create('test.tiff', cols, rows, 1, gdal.GDT_Byte)
old.SetGeoTransform((tl[1], deltaX/cols, 0, tl[0], 0, -deltaY/rows))
outband = old.GetRasterBand(1)
outband.WriteArray(array[:,:,0])
outRasterSRS = osr.SpatialReference()
outRasterSRS.ImportFromEPSG(4326)
outRaster.SetProjection(outRasterSRS.ExportToWkt())
error_threshold = 0.00001
resampling=gdal.GRA_NearestNeighbour
new = gdal.AutoCreateWarpedVRT( old,
None,
outRasterSRS.ExportToWkt(),
resampling,
error_threshold)
outband.FlushCache()
return (old, new)
def getPositionOfSymbolUsingGDAL(rasters, symbol):
# rasters is the output from array2Gtiff
old = rasters[0].GetProjectionRef()
new = rasters[1].GetProjectionRef()
x = symbol[0] #The xth pixel of the image - I want the lon equivalent of this pixel value.
y = symbol[1] # The yth pixel of the image - I want the lat equivalent of this pixel value.
xoff, a, b, yoff, d, e = rasters[0].GetGeoTransform()
xp = (a * x + b * y + xoff)
yp = (d * x + e * y + yoff)
old_cs= osr.SpatialReference()
old_cs.ImportFromWkt(old)
new_cs= osr.SpatialReference()
new_cs.ImportFromWkt(new)
t = osr.CoordinateTransformation(old_cs, new_cs)
lat, lon, z = t.TransformPoint(xp, yp)
return (lon, lat)
This is where I am confused. Right now I feel like I'm using the pixels as the source coordinates. Is this the same thing as just linear interpolation? I no longer use GCP and just use the geotransform.
Will I get more accurate values if I convert to meters and use UTM? Why would using meters instead of pixels change the accuracy of the conversion?
SetGeoTransform()
Per gdal.org/user/raster_data_model.html#affine-geotransform you should do corners or GCPs, not both.getPositionOfSymbolUsingGDAL()
function, I'm not sure which are rasters[0] and [1]. If you have the pixel coords in x,y relative to the original, unwarped image, and its (UTM? EPSG:3395?) CRS, do the same translation x,y -> posX,posY in that source CRS (meters from origin), then convert it to EPSG:4326 in the same way to get degrees lon,lat.