Skip to content

navibyte/geospatial

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

🧭 Geospatial tools for Dart

License Twitter URL style: very good analysis

Geospatial data structures, tools and utilities for Dart and Flutter - coordinates, geometries, feature objects, metadata, spherical geodesy, projections, tiling schemes, vector data models and formats, and geospatial Web APIs.

Dart Flutter

✨ New (2024-04-22): The stable version 1.1.0 adds support for Newline-delimited GeoJSON, EWKT and EWKB. See also the article Decode and encode GeoJSON, WKT and WKB in Dart and Flutter apps.

✨ New (2023-10-29): The stable version 1.0.0 is now ready. See also the article Geospatial tools for Dart - version 1.0 published at Medium.

📦 Packages

Dart code packages published at pub.dev:

Code Package Description
🌐 geobase pub package Geospatial data structures (coordinates, geometries, features, metadata), spherical geodesy, projections and tiling schemes. Vector data format support for GeoJSON, WKT and WKB.
🌎 geodata pub package Geospatial feature service Web APIs with support for GeoJSON and OGC API Features clients.

✨ Features

Key features of the geobase package:

  • 🌐 geographic (longitude-latitude) and projected positions and bounding boxes
  • 📐 spherical geodesy functions for great circle and rhumb line paths
  • 🧩 simple geometries (point, line string, polygon, multi point, multi line string, multi polygon, geometry collection)
  • 🔷 features (with id, properties and geometry) and feature collections
  • 📅 temporal data structures (instant, interval) and spatial extents
  • 📃 vector data formats supported (GeoJSON, Newline-delimited GeoJSON, WKT, WKB )
  • 🗺️ coordinate projections (web mercator + based on the external proj4dart library)
  • 🔢 tiling schemes and tile matrix sets (web mercator, global geodetic)

Key features of the geodata package:

  • 🪄 Client-side data source abstraction for geospatial feature service Web APIs.
  • 🌐 The GeoJSON client to read features from static web resources and local files, supports also Newline-delimited GeoJSON data.
  • 🌎 The OGC API Features client to access metadata and feature items from a compliant geospatial Web API providing GeoJSON data.

Client-side support for the OGC API Features standard:

Standard part Support in this package
OGC API - Features - Part 1: Core Supported for accessing metadata and GeoJSON feature collections.
OGC API - Features - Part 2: Coordinate Reference Systems by Reference Supported.
OGC API - Features - Part 3: Filtering (draft) Partially supported (conformance classes, queryables, features filter).

⌨️ Sample code

Geodesy functions with geobase

Spherical geodesy functions for great circle (shown below) and rhumb line paths:

  final greenwich = Geographic.parseDms(lat: '51°28′40″ N', lon: '0°00′05″ W');
  final sydney = Geographic.parseDms(lat: '33.8688° S', lon: '151.2093° E');

  // Distance (~ 16988 km)
  greenwich.spherical.distanceTo(sydney);

  // Initial and final bearing: 61° -> 139°
  greenwich.spherical.initialBearingTo(sydney);
  greenwich.spherical.finalBearingTo(sydney);

  // Destination point (10 km to bearing 61°): 51° 31.3′ N, 0° 07.5′ E
  greenwich.spherical.destinationPoint(distance: 10000, bearing: 61.0);

  // Midpoint: 28° 34.0′ N, 104° 41.6′ E
  greenwich.spherical.midPointTo(sydney);

Geospatial data structures with geobase

As a quick sample, this is how geometry objects with 2D coordinate are created using geobase:

Geometry Shape Dart code to build objects
Point Point.build([30.0, 10.0])
LineString LineString.build([30, 10, 10, 30, 40, 40])
Polygon Polygon.build([[30, 10, 40, 40, 20, 40, 10, 20, 30, 10]])
Polygon (with a hole) Polygon.build([[35, 10, 45, 45, 15, 40, 10, 20, 35, 10], [20, 30, 35, 35, 30, 20, 20, 30]])
MultiPoint MultiPoint.build([[10, 40], [40, 30], [20, 20], [30, 10]])
MultiLineString MultiLineString.build([[10, 10, 20, 20, 10, 40], [40, 40, 30, 30, 40, 20, 30, 10]])
MultiPolygon MultiPolygon.build([[[30, 20, 45, 40, 10, 40, 30, 20]], [[15, 5, 40, 10, 10, 20, 5, 10, 15, 5]]])
MultiPolygon (with a hole) MultiPolygon.build([[[40, 40, 20, 45, 45, 30, 40, 40]], [[20, 35, 10, 30, 10, 10, 30, 5, 45, 20, 20, 35], [30, 20, 20, 15, 20, 25, 30, 20]]])
GeometryCollection GeometryCollection([Point.build([30.0, 10.0]), LineString.build([10, 10, 20, 20, 10, 40]), Polygon.build([[40, 40, 20, 45, 45, 30, 40, 40]])])

Geospatial feature and feature collections can be instantiated easily too:

  // A geospatial feature collection (with two features):
  FeatureCollection([
    Feature(
      id: 'ROG',
      // a point geometry with a position (lon, lat, elev)
      geometry: Point.build([-0.0014, 51.4778, 45.0]),
      properties: {
        'title': 'Royal Observatory',
        'city': 'London',
        'isMuseum': true,
      },
    ),
    Feature(
      id: 'TB',
      // a point geometry with a position (lon, lat)
      geometry: Point.build([-0.075406, 51.5055]),
      properties: {
        'title': 'Tower Bridge',
        'built': 1886,
      },
    ),
  ]);

GeoJSON, WKT and WKB with geobase

More details in the article (2024-04-14) Decode and encode GeoJSON, WKT and WKB in Dart and Flutter apps.

GeoJSON, WKT and WKB formats are supported as input and output:

  // Parse a geometry from GeoJSON text.
  final geometry = LineString.parse(
    '{"type": "LineString", "coordinates": [[30,10],[10,30],[40,40]]}',
    format: GeoJSON.geometry,
  );

  // Encode a geometry as GeoJSON text.
  print(geometry.toText(format: GeoJSON.geometry));

  // Encode a geometry as WKT text.
  print(geometry.toText(format: WKT.geometry));

  // Encode a geometry as WKB bytes.
  final bytes = geometry.toBytes(format: WKB.geometry);

  // Decode a geometry from WKB bytes.
  LineString.decode(bytes, format: WKB.geometry);

A sample showing more deeply how to handle WKB and EWKB binary data:

  // to get a sample point, first parse a 3D point from WKT encoded string
  final p = Point.parse('POINT Z(-0.0014 51.4778 45)', format: WKT.geometry);

  // to encode a geometry as WKB/EWKB use toBytes() or toBytesHex() methods

  // encode as standard WKB data (format: `WKB.geometry`), prints:
  // 01e9030000c7bab88d06f056bfb003e78c28bd49400000000000804640
  final wkbHex = p.toBytesHex(format: WKB.geometry);
  print(wkbHex);

  // encode as Extended WKB data (format: `WKB.geometryExtended`), prints:
  // 0101000080c7bab88d06f056bfb003e78c28bd49400000000000804640
  final ewkbHex = p.toBytesHex(format: WKB.geometryExtended);
  print(ewkbHex);

  // otherwise encoded data equals, but bytes for the geometry type varies

  // there are some helper methods to analyse WKB/EWKB bytes or hex strings
  // (decodeFlavor, decodeEndian, decodeSRID and versions with hex postfix)

  // prints: "WkbFlavor.standard - WkbFlavor.extended"
  print('${WKB.decodeFlavorHex(wkbHex)} - ${WKB.decodeFlavorHex(ewkbHex)}');

  // when decoding WKB or EWKB data, a variant is detected automatically, so
  // both `WKB.geometry` and `WKB.geometryExtended` can be used
  final pointFromWkb = Point.decodeHex(wkbHex, format: WKB.geometry);
  final pointFromEwkb = Point.decodeHex(ewkbHex, format: WKB.geometry);
  print(pointFromWkb.equals3D(pointFromEwkb)); // prints "true"

  // SRID can be encoded only on EWKB data, this sample prints:
  // 01010000a0e6100000c7bab88d06f056bfb003e78c28bd49400000000000804640
  final ewkbHexWithSRID =
      p.toBytesHex(format: WKB.geometryExtended, crs: CoordRefSys.EPSG_4326);
  print(ewkbHexWithSRID);

  // if you have WKB or EWKB data, but not sure which, then you can fist check
  // a flavor and whether it contains SRID, prints: "SRID from EWKB data: 4326"
  if (WKB.decodeFlavorHex(ewkbHexWithSRID) == WkbFlavor.extended) {
    final srid = WKB.decodeSRIDHex(ewkbHexWithSRID);
    if (srid != null) {
      print('SRID from EWKB data: $srid');

      // after finding out CRS, an actual point can be decoded
      // Point.decodeHex(ewkbHexWithSRID, format: WKB.geometry);
    }
  }

Using Newline-delimited GeoJSON (or "GeoJSONL") is as easy as using the standard GeoJSON:

  /// a feature collection encoded as GeoJSONL and containing two features that
  /// are delimited by the newline character \n
  const sample = '''
    {"type":"Feature","id":"ROG","geometry":{"type":"Point","coordinates":[-0.0014,51.4778,45]},"properties":{"title":"Royal Observatory","place":"Greenwich"}}
    {"type":"Feature","id":"TB","geometry":{"type":"Point","coordinates":[-0.075406,51.5055]},"properties":{"title":"Tower Bridge","built":1886}}
    ''';

  // parse a FeatureCollection object using the decoder for the GeoJSONL format
  final collection = FeatureCollection.parse(sample, format: GeoJSONL.feature);

  // ... use features read and returned in a feature collection object ...

  // encode back to GeoJSONL data
  print(collection.toText(format: GeoJSONL.feature, decimals: 5));

Access GeoJSON resources with geodata

The geodata package has the following diagram describing a decision flowchart how to select a client class to access GeoJSON features:

Quick start code to access a Web API service conforming to OGC API Features:

// 1. Get a client instance for a Web API endpoint.
final client = OGCAPIFeatures.http(endpoint: Uri.parse('...'));

// 2. Access/check metadata (meta, OpenAPI, conformance, collections) as needed.
final conformance = await client.conformance();
if (!conformance.conformsToFeaturesCore(geoJSON: true)) {
  return; // not conforming to core and GeoJSON - so return
}

// 3. Get a feature source for a specific collection.
final source = await client.collection('my_collection');

// 4. Access (and check) metadata for this collection.
final meta = await source.meta();
print('Collection title: ${meta.title}');

// 5. Access feature items.
final items = await source.itemsAll(limit: 100);

// 6. Check response metadata.
print('Timestamp: ${items.timeStamp}');

// 7. Get an iterable of feature objects.
final features = items.collection.features;

// 8. Loop through features (each with id, properties and geometry)
for (final feat in features) {
  print('Feature ${feat.id} with geometry: ${feat.geometry}');
}

🚀 Demos and samples

✨ See also the Geospatial demos for Dart code repository for demo and sample apps demonstrating the usage of geobase and geodata packages along with other topics.

Code Description
earthquake_map Shows earthquakes fetched from the USGS web service on a basic map view. The demo uses both geobase and geodata packages for geospatial data accesss. Discusses also state management based on Riverpod. The map UI is based on the Google Maps Flutter plugin.

🗞️ News

2024-04-22

2023-10-29

2023-09-30

2023-08-11

  • geobase version 0.5.0
    • ✨ Spherical geodesy functions (distance, bearing, destination point, etc.) for great circle and rhumb line paths.
  • geodata version 0.12.0
    • ✨ Better client-side support for OGC API Features (Part 1 and 2).

See also older news in the changelog of this repository.

🏗️ Roadmap

🧩 See open issues for planned features, requests for change, and observed bugs.

💡 Any comments, questions, suggestions of new features and other other contributions are welcome, of course!

🪄 Active packages in this repository:

⚠️ Not active packages in this repository:

🏡 Authors

This project is authored by Navibyte.

©️ License

The project

This project is licensed under the "BSD-3-Clause"-style license.

Please see the LICENSE.

Included derivative work

This project contains portions of derivative work:

Source repositories used when porting functionality to Dart and this project:

  • geodesy by Chris Veness 2002-2022

⭐ Links and other resources

Some external links and other resources.

Geospatial data formats and APIs

Geospatial:

OGC (The Open Geospatial Consortium) related:

W3C

The OpenAPI Initiative (OAI)

Dart and Flutter programming

SDKs:

Latest on Dart SDKs

  • Dart 3.3 with extension types, evolving JavaScript-interoperability and experimental support for WebAssembly.
  • Dart 3.2 with improved language & developer experience.
  • Dart 3 with 100% sound null safety, new features (records, patterns, and class modifiers), and a peek into the future.
  • Dart 3 alpha with records, patterns, access controls, portability advancements and the new Dart 3 type system (100% sound null safety)
  • Dart 2.18 with Objective-C & Swift interop, and improved type inference
  • Dart 2.17 with enum member support, parameter forwarding to super classes, flexibility for named parameters, and more
  • Dart 2.16 with improved tooling and platform handling
  • Dart 2.15 with fast concurrency, constructor tear-offs, improved enums, and more
  • Dart 2.14 with Apple Silicon support, default lints etc.
  • Dart 2.13 with new type aliases and more
  • Dart 2.12 with sound null safety

Latest on Flutter SDKs

Packages

Dart 3 migration

Null-safety (Dart 2):

Guidelines

Roadmaps

Dart and Flutter libraries

There are thousands of excellent libraries available at pub.dev.

Here listed only those that are used (depended directly) by code packages of this repository:

Package @ pub.dev Code @ GitHub Description
equatable felangel/equatable Simplify Equality Comparisons
http dart-lang/http A composable API for making HTTP requests in Dart.
meta dart-lang/sdk This package defines annotations that can be used by the tools that are shipped with the Dart SDK.
proj4dart maRci002/proj4dart Proj4dart is a Dart library to transform point coordinates from one coordinate system to another, including datum transformations (Dart version of proj4js/proj4js).
very_good_analysis VeryGoodOpenSource/very_good_analysis Lint rules for Dart and Flutter.

In some previous releases also following are utilized:

Package @ pub.dev Code @ GitHub Description
synchronized tekartik/synchronized.dart Basic lock mechanism to prevent concurrent access to asynchronous code.