crossbind
GitHub

GDAL for iOS

v3.13.3iOS

GDAL 3.13.3 for React Native apps on iOS, precompiled for arm64 devices and simulators as @crossbind/port-gdal-ios.

npm install @crossbind/port-gdal-ios@beta

Install

shell
npm install @crossbind/plugin-react-native@beta @crossbind/plugin-react-native-ios-helper@beta @crossbind/port-gdal-ios@beta
npm install --save-dev @crossbind/plugin-metro@beta
cd ios && pod install
crossbind.config.mjs
import gdalIos from '@crossbind/port-gdal-ios/crossbind.config.js';
 
export default {
dependencies: [gdalIos],
paths: { config: import.meta.url },
};
metro.config.js
const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config');
const CrossbindMetroPlugin = require('@crossbind/plugin-metro');
 
const defaultConfig = getDefaultConfig(__dirname);
 
const config = {
...CrossbindMetroPlugin(defaultConfig),
};
 
module.exports = mergeConfig(defaultConfig, config);

The whole flow, including Expo, is in the React Native playbook.

Usage

The examples the WebAssembly page runs, as iOS compiles them: the same headers and the same calls. They are checked on the WebAssembly build.

Convert GeoJSON to GeoPackage and Shapefile, reprojected

Format conversion is what GDAL is used for most, as the ogr2ogr tool: GDALVectorTranslate takes the same arguments, here -f for the format and -t_srs for the coordinate system. The input is text handed over through /vsimem/, and a zipped Shapefile is one file.

src/native/vector_converter.h
#pragma once
 
#include <cpl_error.h>
#include <cpl_string.h>
#include <cpl_vsi.h>
#include <gdal.h>
#include <gdal_utils.h>
#include <ogr_srs_api.h>
#include <ogrsf_frmts.h>
 
#include <cmath>
#include <stdexcept>
#include <string>
 
// Converts GeoJSON text to another vector format, reprojected, and reports what was written.
// It registers only the drivers it uses, so GDAL opens and writes these formats and no others.
class VectorConverter {
public:
VectorConverter() {
RegisterOGRGeoJSON();
RegisterOGRGeoPackage();
RegisterOGRFlatGeobuf();
RegisterOGRShape();
}
 
std::string convert(const std::string& geojson, const std::string& format, const std::string& targetCrs,
const std::string& outputPath) {
const char* input = "/vsimem/input.geojson";
VSIFCloseL(VSIFileFromMemBuffer(input, reinterpret_cast<GByte*>(const_cast<char*>(geojson.data())), geojson.size(), FALSE));
GDALDatasetH source = GDALOpenEx(input, GDAL_OF_VECTOR, nullptr, nullptr, nullptr);
if (!source) fail(input);
 
CPLStringList args;
args.AddString("-f");
args.AddString(format.c_str());
args.AddString("-t_srs");
args.AddString(targetCrs.c_str());
GDALVectorTranslateOptions* options = GDALVectorTranslateOptionsNew(args.List(), nullptr);
VSIUnlink(outputPath.c_str()); // replace the output of an earlier call
GDALDatasetH written = GDALVectorTranslate(outputPath.c_str(), nullptr, 1, &source, options, nullptr);
GDALVectorTranslateOptionsFree(options);
GDALClose(source);
if (!written) fail(input);
 
OGRLayerH layer = GDALDatasetGetLayer(written, 0);
OGREnvelope extent;
OGR_L_GetExtent(layer, &extent, TRUE);
OGRSpatialReferenceH crs = OGR_L_GetSpatialRef(layer);
const char* authority = crs ? OSRGetAuthorityName(crs, nullptr) : nullptr;
const char* code = crs ? OSRGetAuthorityCode(crs, nullptr) : nullptr;
const std::string summary = format + ": " + std::to_string(OGR_L_GetFeatureCount(layer, TRUE)) + " features, " +
(authority && code ? std::string(authority) + ":" + code : std::string("no CRS")) + ", extent " +
std::to_string(std::llround(extent.MinX)) + " " + std::to_string(std::llround(extent.MinY)) + " " +
std::to_string(std::llround(extent.MaxX)) + " " + std::to_string(std::llround(extent.MaxY));
GDALClose(written);
VSIUnlink(input);
return summary;
}
 
private:
[[noreturn]] static void fail(const char* input) {
const std::string reason = CPLGetLastErrorMsg();
VSIUnlink(input);
throw std::runtime_error(reason.empty() ? "GDAL could not convert the data" : reason);
}
};
main.js
import { initNative, VectorConverter } from './native/vector_converter.h';
 
await initNative();
const converter = await new VectorConverter();
const cities = JSON.stringify({
type: 'FeatureCollection',
features: [
['Istanbul', 28.9784, 41.0082],
['Ankara', 32.8597, 39.9334],
['Izmir', 27.1428, 38.4237],
].map(([name, lon, lat]) => ({ type: 'Feature', properties: { name }, geometry: { type: 'Point', coordinates: [lon, lat] } })),
});
console.log(await converter.convert(cities, 'GPKG', 'EPSG:3857', '/vsimem/cities.gpkg'));
console.log(await converter.convert(cities, 'ESRI Shapefile', 'EPSG:32635', '/vsimem/cities.shp.zip'));
PRINTS
GPKG: 3 features, EPSG:3857, extent 3021523 4639455 3657925 5013551
ESRI Shapefile: 3 features, EPSG:32635, extent 512465 4252837 1000822 4541552

Write a GeoTIFF and read its georeferencing back

GDALCreate writes a raster with GDALSetGeoTransform and a CRS from OSRImportFromEPSG; GDALOpenEx opens it again as it opens any of the formats GDAL reads, and the size, band type, geotransform and CRS come back from the dataset. GDALInfo returns the report the gdalinfo tool prints.

src/native/raster_info.h
#pragma once
 
#include <cpl_error.h>
#include <cpl_string.h>
#include <gdal.h>
#include <gdal_frmts.h>
#include <gdal_utils.h>
#include <ogr_srs_api.h>
 
#include <cstdio>
#include <sstream>
#include <stdexcept>
#include <string>
#include <vector>
 
// Writes a GeoTIFF with a coordinate system, then opens it the way any raster is opened and reads
// what GDAL knows about it: the size, the bands, where the pixels lie (the geotransform) and in which CRS.
class RasterInfo {
public:
RasterInfo() { GDALRegister_GTiff(); }
 
// Elevations in metres that rise by 1 m a pixel to the east and 2 m a pixel to the south.
void create(const std::string& path, int width, int height, double west, double north, double pixelSize, int epsg) {
char** options = CSLSetNameValue(nullptr, "COMPRESS", "DEFLATE");
GDALDatasetH dataset = GDALCreate(GDALGetDriverByName("GTiff"), path.c_str(), width, height, 1, GDT_Float32, options);
CSLDestroy(options);
if (!dataset) fail();
double transform[6] = {west, pixelSize, 0, north, 0, -pixelSize};
GDALSetGeoTransform(dataset, transform);
OGRSpatialReferenceH crs = OSRNewSpatialReference(nullptr);
OSRImportFromEPSG(crs, epsg);
GDALSetSpatialRef(dataset, crs);
OSRDestroySpatialReference(crs);
GDALRasterBandH band = GDALGetRasterBand(dataset, 1);
std::vector<float> row(width);
for (int y = 0; y < height; ++y) {
for (int x = 0; x < width; ++x) row[x] = static_cast<float>(100 + x + 2 * y);
if (GDALRasterIO(band, GF_Write, 0, y, width, 1, row.data(), width, 1, GDT_Float32, 0, 0) != CE_None) {
GDALClose(dataset);
fail();
}
}
GDALClose(dataset);
}
 
std::string describe(const std::string& path) {
GDALDatasetH dataset = GDALOpenEx(path.c_str(), GDAL_OF_RASTER, nullptr, nullptr, nullptr);
if (!dataset) fail();
GDALRasterBandH band = GDALGetRasterBand(dataset, 1);
double t[6];
GDALGetGeoTransform(dataset, t);
OGRSpatialReferenceH crs = GDALGetSpatialRef(dataset);
double range[2];
GDALComputeRasterMinMax(band, FALSE, range);
 
char line[160];
std::string text = std::string(GDALGetDriverShortName(GDALGetDatasetDriver(dataset))) + ", " +
std::to_string(GDALGetRasterXSize(dataset)) + " x " + std::to_string(GDALGetRasterYSize(dataset)) + " pixels, " +
std::to_string(GDALGetRasterCount(dataset)) + " band of " + GDALGetDataTypeName(GDALGetRasterDataType(band)) + "\n";
std::snprintf(line, sizeof line, "origin %.0f, %.0f; pixel size %.0f x %.0f\n", t[0], t[3], t[1], t[5]);
text += line;
text += std::string(OSRGetName(crs)) + ", " + OSRGetAuthorityName(crs, nullptr) + ":" + OSRGetAuthorityCode(crs, nullptr) + "\n";
std::snprintf(line, sizeof line, "values %.0f to %.0f\n", range[0], range[1]);
text += line;
 
// GDALInfo returns the report the gdalinfo tool prints; keep its corner coordinates.
char* report = GDALInfo(dataset, nullptr);
std::istringstream lines(report ? report : "");
CPLFree(report);
GDALClose(dataset);
for (std::string entry; std::getline(lines, entry);) {
if (entry.rfind("Upper Left", 0) == 0 || entry.rfind("Lower Right", 0) == 0) text += entry + "\n";
}
return text.substr(0, text.size() - 1);
}
 
private:
[[noreturn]] static void fail() {
const std::string reason = CPLGetLastErrorMsg();
throw std::runtime_error(reason.empty() ? "GDAL could not read the raster" : reason);
}
};
main.js
import { initNative, RasterInfo } from './native/raster_info.h';
 
await initNative();
const raster = await new RasterInfo();
// 200 x 150 pixels of 30 m, the top left corner at 500000 E 4450000 N in UTM zone 35N
await raster.create('/vsimem/dem.tif', 200, 150, 500000, 4450000, 30, 32635);
for (const line of (await raster.describe('/vsimem/dem.tif')).split('\n')) console.log(line);
PRINTS
GTiff, 200 x 150 pixels, 1 band of Float32
origin 500000, 4450000; pixel size 30 x -30
WGS 84 / UTM zone 35N, EPSG:32635
values 100 to 597
Upper Left  (  500000.000, 4450000.000) ( 27d 0' 0.00"E, 40d12' 1.44"N)
Lower Right (  506000.000, 4445500.000) ( 27d 4'13.64"E, 40d 9'35.41"N)

Reproject a raster and write a Cloud-Optimized GeoTIFF

GDALWarp reprojects as the gdalwarp tool does, here into a virtual raster that is computed while it is read; GDALTranslate with -of COG then writes it tiled, compressed and with overviews, the layout web maps read with HTTP range requests.

src/native/cog_writer.h
#pragma once
 
#include <cpl_error.h>
#include <cpl_string.h>
#include <cpl_vsi.h>
#include <gdal.h>
#include <gdal_frmts.h>
#include <gdal_utils.h>
 
#include <cstdio>
#include <stdexcept>
#include <string>
 
// Reprojects a raster as the gdalwarp tool does, then writes it as gdal_translate -of COG does: a
// Cloud-Optimized GeoTIFF, tiled and compressed, with overviews laid out so that a client can read
// one area at one zoom level with a few HTTP range requests.
class CogWriter {
public:
CogWriter() {
GDALRegister_GTiff();
GDALRegister_COG();
GDALRegister_VRT();
}
 
std::string warpToCog(const std::string& source, const std::string& targetCrs, const std::string& output) {
GDALDatasetH input = GDALOpenEx(source.c_str(), GDAL_OF_RASTER, nullptr, nullptr, nullptr);
if (!input) fail();
 
// gdalwarp -of VRT: the reprojected raster stays virtual and is computed while the COG is written.
CPLStringList warpArgs;
for (const char* arg : {"-of", "VRT", "-r", "bilinear", "-t_srs"}) warpArgs.AddString(arg);
warpArgs.AddString(targetCrs.c_str());
GDALWarpAppOptions* warpOptions = GDALWarpAppOptionsNew(warpArgs.List(), nullptr);
GDALDatasetH warped = GDALWarp("", nullptr, 1, &input, warpOptions, nullptr);
GDALWarpAppOptionsFree(warpOptions);
if (!warped) {
GDALClose(input);
fail();
}
 
CPLStringList cogArgs;
for (const char* arg : {"-of", "COG", "-co", "COMPRESS=DEFLATE", "-co", "BLOCKSIZE=256"}) cogArgs.AddString(arg);
GDALTranslateOptions* cogOptions = GDALTranslateOptionsNew(cogArgs.List(), nullptr);
VSIUnlink(output.c_str());
GDALDatasetH cog = GDALTranslate(output.c_str(), warped, cogOptions, nullptr);
GDALTranslateOptionsFree(cogOptions);
GDALClose(warped);
GDALClose(input);
if (!cog) fail();
GDALClose(cog);
return describe(output);
}
 
private:
// What a reader of the file sees.
static std::string describe(const std::string& path) {
GDALDatasetH dataset = GDALOpenEx(path.c_str(), GDAL_OF_RASTER, nullptr, nullptr, nullptr);
if (!dataset) fail();
GDALRasterBandH band = GDALGetRasterBand(dataset, 1);
double t[6];
GDALGetGeoTransform(dataset, t);
int blockWidth = 0, blockHeight = 0;
GDALGetBlockSize(band, &blockWidth, &blockHeight);
const char* layout = GDALGetMetadataItem(dataset, "LAYOUT", "IMAGE_STRUCTURE");
const char* compression = GDALGetMetadataItem(dataset, "COMPRESSION", "IMAGE_STRUCTURE");
 
char line[200];
std::snprintf(line, sizeof line, "%d x %d pixels of %.6f x %.6f degrees\nLAYOUT=%s, COMPRESSION=%s, %d x %d blocks\noverviews",
GDALGetRasterXSize(dataset), GDALGetRasterYSize(dataset), t[1], -t[5], layout ? layout : "none",
compression ? compression : "none", blockWidth, blockHeight);
std::string text = line;
for (int i = 0; i < GDALGetOverviewCount(band); ++i) {
GDALRasterBandH overview = GDALGetOverview(band, i);
text += (i ? ", " : " ") + std::to_string(GDALGetRasterBandXSize(overview)) + " x " + std::to_string(GDALGetRasterBandYSize(overview));
}
GDALClose(dataset);
return text;
}
 
[[noreturn]] static void fail() {
const std::string reason = CPLGetLastErrorMsg();
throw std::runtime_error(reason.empty() ? "GDAL could not write the COG" : reason);
}
};
main.js
import { initNative, CogWriter } from './native/cog_writer.h';
import { RasterInfo } from './native/raster_info.h';
 
await initNative();
const raster = await new RasterInfo();
await raster.create('/vsimem/utm.tif', 1000, 800, 500000, 4450000, 30, 32635); // 30 x 24 km in UTM zone 35N
const writer = await new CogWriter();
const report = await writer.warpToCog('/vsimem/utm.tif', 'EPSG:4326', '/vsimem/cog.tif');
for (const line of report.split('\n')) console.log(line);
PRINTS
1093 x 672 pixels of 0.000322 x 0.000322 degrees
LAYOUT=COG, COMPRESSION=DEFLATE, 256 x 256 blocks
overviews 546 x 336, 273 x 168, 136 x 84

Hillshade, slope and contour lines from an elevation model

GDALDEMProcessing is the gdaldem tool: hillshade, slope, aspect, roughness, TRI and TPI by name. GDALContourGenerateEx draws the contour lines gdal_contour draws, into any vector layer; here an in-memory one, measured with OGR_G_Length.

src/native/dem_tools.h
#pragma once
 
#include <cpl_error.h>
#include <cpl_string.h>
#include <gdal.h>
#include <gdal_alg.h>
#include <gdal_frmts.h>
#include <gdal_utils.h>
#include <ogr_api.h>
#include <ogr_srs_api.h>
 
#include <algorithm>
#include <cstdio>
#include <stdexcept>
#include <string>
#include <vector>
 
// Terrain products from an elevation model: the gdaldem tool's hillshade and slope, and the contour
// lines gdal_contour draws.
class DemTools {
public:
DemTools() {
GDALRegister_GTiff();
GDALRegister_MEM();
}
 
// A round hill, 900 m at the centre of a 100 m plain, in whole metres.
void createHill(const std::string& path, int size, double pixelSize) {
GDALDatasetH dataset = GDALCreate(GDALGetDriverByName("GTiff"), path.c_str(), size, size, 1, GDT_Float32, nullptr);
if (!dataset) fail();
double transform[6] = {500000, pixelSize, 0, 4450000, 0, -pixelSize};
GDALSetGeoTransform(dataset, transform);
OGRSpatialReferenceH crs = OSRNewSpatialReference(nullptr);
OSRImportFromEPSG(crs, 32635);
GDALSetSpatialRef(dataset, crs);
OSRDestroySpatialReference(crs);
std::vector<float> row(size);
const int centre = size / 2;
for (int y = 0; y < size; ++y) {
for (int x = 0; x < size; ++x) {
const int dx = x - centre, dy = y - centre;
row[x] = static_cast<float>(std::max(100, 900 - (dx * dx + dy * dy) / 4));
}
if (GDALRasterIO(GDALGetRasterBand(dataset, 1), GF_Write, 0, y, size, 1, row.data(), size, 1, GDT_Float32, 0, 0) != CE_None) {
GDALClose(dataset);
fail();
}
}
GDALClose(dataset);
}
 
// gdaldem <processing>: "hillshade", "slope", "aspect", "roughness", "TRI" or "TPI". With
// -compute_edges the border pixels get values too.
std::string derive(const std::string& dem, const std::string& processing, const std::string& output) {
GDALDatasetH source = open(dem);
CPLStringList args;
args.AddString("-compute_edges");
GDALDEMProcessingOptions* options = GDALDEMProcessingOptionsNew(args.List(), nullptr);
GDALDatasetH result = GDALDEMProcessing(output.c_str(), source, processing.c_str(), nullptr, options, nullptr);
GDALDEMProcessingOptionsFree(options);
GDALClose(source);
if (!result) fail();
GDALRasterBandH band = GDALGetRasterBand(result, 1);
double min = 0, max = 0, mean = 0, deviation = 0;
GDALComputeRasterStatistics(band, FALSE, &min, &max, &mean, &deviation, nullptr, nullptr);
const int checksum = GDALChecksumImage(band, 0, 0, GDALGetRasterXSize(result), GDALGetRasterYSize(result));
GDALClose(result);
char line[160];
std::snprintf(line, sizeof line, "%s: %.2f to %.2f, mean %.2f, checksum %d", processing.c_str(), min, max, mean, checksum);
return line;
}
 
// gdal_contour -i <interval>: the lines go to an in-memory layer, one feature per line.
std::string contours(const std::string& dem, double interval) {
GDALDatasetH source = open(dem);
GDALDatasetH store = GDALCreate(GDALGetDriverByName("MEM"), "", 0, 0, 0, GDT_Unknown, nullptr);
OGRLayerH layer = GDALDatasetCreateLayer(store, "contours", GDALGetSpatialRef(source), wkbLineString, nullptr);
OGRFieldDefnH field = OGR_Fld_Create("elevation", OFTReal);
OGR_L_CreateField(layer, field, TRUE);
OGR_Fld_Destroy(field);
 
char value[32];
std::snprintf(value, sizeof value, "%g", interval);
CPLStringList options;
options.SetNameValue("LEVEL_INTERVAL", value);
options.SetNameValue("ELEV_FIELD", "0");
const CPLErr error = GDALContourGenerateEx(GDALGetRasterBand(source, 1), layer, options.List(), nullptr, nullptr);
GDALClose(source);
if (error != CE_None) {
GDALClose(store);
fail();
}
 
int lines = 0;
double lowest = 0, highest = 0, length = 0;
OGR_L_ResetReading(layer);
for (OGRFeatureH feature; (feature = OGR_L_GetNextFeature(layer)) != nullptr; OGR_F_Destroy(feature)) {
const double elevation = OGR_F_GetFieldAsDouble(feature, 0);
lowest = lines ? std::min(lowest, elevation) : elevation;
highest = lines ? std::max(highest, elevation) : elevation;
length += OGR_G_Length(OGR_F_GetGeometryRef(feature));
++lines;
}
GDALClose(store);
char line[160];
std::snprintf(line, sizeof line, "contours every %g m: %d lines from %g to %g m, %.1f km long", interval, lines, lowest, highest, length / 1000);
return line;
}
 
private:
static GDALDatasetH open(const std::string& path) {
GDALDatasetH dataset = GDALOpenEx(path.c_str(), GDAL_OF_RASTER, nullptr, nullptr, nullptr);
if (!dataset) fail();
return dataset;
}
 
[[noreturn]] static void fail() {
const std::string reason = CPLGetLastErrorMsg();
throw std::runtime_error(reason.empty() ? "GDAL could not process the elevation model" : reason);
}
};
main.js
import { initNative, DemTools } from './native/dem_tools.h';
 
await initNative();
const tools = await new DemTools();
await tools.createHill('/vsimem/hill.tif', 101, 30); // 101 x 101 pixels of 30 m
console.log(await tools.derive('/vsimem/hill.tif', 'hillshade', '/vsimem/hillshade.tif'));
console.log(await tools.derive('/vsimem/hill.tif', 'slope', '/vsimem/slope.tif'));
console.log(await tools.contours('/vsimem/hill.tif', 100));
PRINTS
hillshade: 12.00 to 255.00, mean 156.63, checksum 58516
slope: 0.00 to 42.97, mean 27.46, checksum 54054
contours every 100 m: 11 lines from 200 to 900 m, 46.9 km long

Read features and filter them by attribute and area

OGR reads every vector format through one API: the schema from OGR_L_GetLayerDefn, then OGR_L_GetNextFeature over the features that pass OGR_L_SetAttributeFilter, a SQL WHERE clause, and OGR_L_SetSpatialFilterRect. Open options turn the lon and lat columns of a CSV into points.

src/native/feature_query.h
#pragma once
 
#include <cpl_conv.h>
#include <cpl_error.h>
#include <cpl_vsi.h>
#include <gdal.h>
#include <ogr_api.h>
#include <ogrsf_frmts.h>
 
#include <stdexcept>
#include <string>
 
// Reads a layer with OGR: its schema, then the features that pass an attribute filter (a SQL WHERE
// clause) inside a rectangle, with their attributes and geometry.
class FeatureQuery {
public:
FeatureQuery() { RegisterOGRCSV(); }
 
// `csv` is text with lon and lat columns, which the CSV driver turns into point geometries.
std::string select(const std::string& csv, const std::string& where, double west, double south, double east, double north) {
const char* path = "/vsimem/sensors.csv";
VSIFCloseL(VSIFileFromMemBuffer(path, reinterpret_cast<GByte*>(const_cast<char*>(csv.data())), csv.size(), FALSE));
const char* const openOptions[] = {"X_POSSIBLE_NAMES=lon", "Y_POSSIBLE_NAMES=lat", "KEEP_GEOM_COLUMNS=NO", "AUTODETECT_TYPE=YES", nullptr};
GDALDatasetH dataset = GDALOpenEx(path, GDAL_OF_VECTOR, nullptr, openOptions, nullptr);
if (!dataset) fail(path);
OGRLayerH layer = GDALDatasetGetLayer(dataset, 0);
OGRFeatureDefnH schema = OGR_L_GetLayerDefn(layer);
 
std::string text = std::string(OGR_L_GetName(layer)) + ": " + std::to_string(OGR_L_GetFeatureCount(layer, TRUE)) + " features of " +
OGRGeometryTypeToName(OGR_L_GetGeomType(layer)) + ", fields";
for (int i = 0; i < OGR_FD_GetFieldCount(schema); ++i) {
OGRFieldDefnH field = OGR_FD_GetFieldDefn(schema, i);
text += std::string(i ? ", " : " ") + OGR_Fld_GetNameRef(field) + " " + OGR_GetFieldTypeName(OGR_Fld_GetType(field));
}
 
if (OGR_L_SetAttributeFilter(layer, where.c_str()) != OGRERR_NONE) {
GDALClose(dataset);
fail(path);
}
OGR_L_SetSpatialFilterRect(layer, west, south, east, north);
OGR_L_ResetReading(layer);
for (OGRFeatureH feature; (feature = OGR_L_GetNextFeature(layer)) != nullptr; OGR_F_Destroy(feature)) {
text += "\n";
for (int i = 0; i < OGR_F_GetFieldCount(feature); ++i) text += std::string(OGR_F_GetFieldAsString(feature, i)) + " ";
char* wkt = nullptr;
OGR_G_ExportToWkt(OGR_F_GetGeometryRef(feature), &wkt);
text += wkt ? wkt : "";
CPLFree(wkt);
}
GDALClose(dataset);
VSIUnlink(path);
return text;
}
 
private:
[[noreturn]] static void fail(const char* path) {
const std::string reason = CPLGetLastErrorMsg();
VSIUnlink(path);
throw std::runtime_error(reason.empty() ? "GDAL could not read the features" : reason);
}
};
main.js
import { initNative, FeatureQuery } from './native/feature_query.h';
 
await initNative();
const csv = [
'id,kind,reading,lon,lat',
'1,air,31.5,28.9784,41.0082',
'2,air,18.2,32.8597,39.9334',
'3,water,22.7,27.1428,38.4237',
'4,air,12.9,39.7168,41.0027',
'5,water,19.4,30.7133,36.8969',
'6,air,27.3,29.0610,40.1885',
].join('\n');
const query = await new FeatureQuery();
// readings above 20 between 26 and 31 degrees east, 36 and 42 degrees north
const found = await query.select(csv, 'reading > 20', 26, 36, 31, 42);
for (const line of found.split('\n')) console.log(line);
PRINTS
sensors: 6 features of Point, fields id Integer, kind String, reading Real
1 air 31.5 POINT (28.9784 41.0082)
3 water 22.7 POINT (27.1428 38.4237)
6 air 27.3 POINT (29.061 40.1885)

What is different on iOS

  • pod install compiles your headers with the library through the plugin's podspec, and the app build links the result.
  • Named imports from ./native/<header>.h work as on the web: await initNative() once, then call the classes.
  • There is no m.FS and no /memfs: files live in the app's own storage, and your C++ takes their paths.
  • No Worker and no COOP or COEP: runtime: 'mt' uses pthreads directly.

Other platforms

Facts on this page come from the port manifests in the repository and from what npm served on beta when the site was built. See the Libraries guide for the full consumer flow.

MORE LIBRARIES
cURLExpatGEOSGeoTIFFiconvLERClibjpeg-turbolibTIFFOpenSSLPROJSpatiaLiteSQLiteWebPzlibZstandard
Type to search every guide page and section.
↑↓ navigate↵ openesc close