- Open a georeferenced raster and manipulate in OpenCV then save
Raster: e.g. a satellite image, a Digital Elevation Model, a radar map
Common raster formats are GeoTIFF and JPEG2000. You use RasterMap
to
work with rasters.
Vector: e.g. a shape of country boundaries, points of interest in a city.
Common vector formats are GeoJSON files, Google Earth KML files, and
ESRI Shapefiles (a directory of .shp, .shx, .dbf). You use VectorMap
to
work with vectors.
Each Map references somewhere in the world. There are different ways of specifying this. Latitude and Longitude are very common.
Maps with rasters have two coordinate systems: one to identify the pixel on the raster and the other to identify where in the world a point on the map is.
For example, the upper-left pixel of a raster has x coordinate of 0 and y coordinate of 0. That pixel may be located on the world at 36.9°S and 174.8°E.
The naming convention x
,y
is used for raster coordinates
and e
(for easting), n
(for northing) are used
for world coordinates. This allows us to easily specify both coordinates
in the same scope.
You can convert world coordinates to raster coordinates this way:
var map = geomap.open("picture.tif") let (x, y) = map.worldToPixel(-36.9, 174.8)
This example uses latitude and longitude world coordinates. You must
pass world coordinates in the same Coordinate Reference System the raster
uses. If the image above used UTM60 as a Coordinate Reference System then
you would get an incorrect x,y value if you passed in latitude, longitude.
Vector shapes often define boundaries, such as property, or markers, like survey points. For example, you can read a KML file created on Google Earth that identifies points of interest in a city and the city boundaries.
Vectors are arranged in one or more Layers. Each Layer has one or more Features. Each Feature has one or more Geometries (such as a polygon, or a point), and some attributes (describing the Feature, e.g. "My House").
To read vectors, open a VectorMap
with geomap.openVector
then you can
iterate through all the vector data:
for layer in map.layers: for feature in layer.features: for geometry in feature.geometries echo geometry
Often you want to take a vector shape and do something to a raster based on it. For example, you want an image to be transparent outside a polygon, or an image to have the outline of a line drawn on the raster.
To start you need a map of the raster and a map of the vectors. As an example,
say we have a raster file which is an RGB satellite image of our country, and a
vector file that gives city and country boundaries:
map boundaries = geomap.openVector("boundaries.kml") # state and country boundaries map image = geomap.openRaster("landscape.tif") #satellite image in RGB format
let border = boundaries.layers[0].features[0] # assume this is a polygon
let mask = border.createMaskOn(image, mskExterior, pen) # mskBorder, mskterior using the pen
image &&= mask # all pixels on the outside will
# become black, the remaining stay
# as they are
SDL and Java use [x...][y...] while opencv uses [(x,y), (x,y)]
poly: tuple[T][x: seq[T], y: seq[T]] let poly = border.createPolygonOn(image) let rect = border.createRectOn(image) let line = border.createLineOn(image)
dataset operator array1D # apply operator on all bands in dataset with 1D array (dataset is lhs, array is rhs)
band operator array1D
e.g.
let mask = [[0x00, 0xFF, 0x00], [0x00, 0xFF, 0x00]] let result = map + mask # applies mask to all the map let result = map.bands[0] && mask # applies mask to one band in the map
To do this in-place and actually modify the raster:
map += mask # map raster is overwritten map.bands[0] &&= mask # bands raster is overwritten
e.g. NDVI import arraymancer
proc ndvi[T](nir: T, red: T): nir + red / nir - red
let tensor = map.toArray().toTensor() #toArray gives shape Channel xHeight x Width let red = map.bands[4] let nir = map.bands[7] map2(red.toArray, ndvi, nir.toArray) # arraymancer
for x = 0 x < width: for y = 0 y < height: # faster way of iterating is just one loop let i = x * width + y result[i] = red[i] + nir[i] / red[i] - nir[i]
calc does not operate on NO_DATA values - they remain the same. If you don't want that, remove NO_DATA values with map.noDataTo(value: uint8 etc) first
Doing in-place operations requires write permission to storage and the driver to support it along with AddBand() and RemoveBand(). As soon as we do in-place writes, we may need to create a new dataset and destroy the old one TODO: Reading large rasters one chunk at a time, process them, then read next chunk, process it, etc (probably do this using an iterator)
Raster maps have a pointer to the raster data if it has been read which can be used to manipulate the raster with other libraries. The raster data can be arranged in a variety of ways to be compatible with these libraries.
This data can be changed in place but it cannot be reduced or increased in the size of the raster, which is defined by the width, height and data type. If you want to change the raster in a way that changes the size in memory or the structure (bands, data type), then you must create a new raster to do this. TODO: Create new raster
let map = geomap.openRaster("image.tif")
let raster = map.readRaster(BIP) # OpenCV needs pixel interleaved format in memory
let mat = constructMat(map.height.cint,
map.width.cint,
toOpenCVType(map.dataType, map.numBands),
raster.rasterData)
# we now have a Mat object we can do things with in OpenCV
# if we want to display it with imshow we must convert to BGR colour space
Map uses GDAL native libraries which must be available on your users system at runtime.
If you distribute using a Homebrew package, you can simply add gdal as a dependancy. This will use dynamic linking at runtime with the dylibs in the same locations as you have. (TODO: check versions)
Either build Map as a framework and include it in your dmg file otherwise statically link Map.
If you distribute using a package manager, add libgdal as a dependancy. This will use dynamic linking at runtime.
If you distribute using chocolate, gdal is not yet a dependancy, and most likely you don't distribute using chocolate. Therefore you either have to build and supply the GDAL and it's required dependancies (e.g. libgif, openjpeg, openssl, etc) for Windows or statically Map and dynamically link to it (Map.dll) or statically compile your entire application.
In some cases, you may want to customise your own GDAL build, for example to exclude certain drivers or statically compile all the dependancies.
- Large images
- Multi-threading