geobase 0.6.0 copy "geobase: ^0.6.0" to clipboard
geobase: ^0.6.0 copied to clipboard

Geospatial data, spherical geodesy, projections, tiling schemes, and vector formats (GeoJSON, WKT, WKB).

example/geobase_example.dart

// Copyright (c) 2020-2023 Navibyte (https://navibyte.com). All rights reserved.
// Use of this source code is governed by a “BSD-3-Clause”-style license that is
// specified in the LICENSE file.
//
// Docs: https://github.com/navibyte/geospatial

// ignore_for_file: avoid_print, prefer_const_constructors, prefer_const_literals_to_create_immutables, cascade_invocations, lines_longer_than_80_chars, avoid_redundant_argument_values, omit_local_variable_types

import 'package:geobase/geobase.dart';

/*
To test run this from command line: 

dart example/geobase_example.dart
*/

void main() {
  // introduction
  _intro();

  // coordinates
  _positionData();
  _positionSeries();
  _geographicCoordinates();
  _geographicCoordinatesDMS();
  _projectedCoordinates();
  _scalableCoordinates();

  // geodesy
  _sphericalGeodesyGreatCircle();
  _sphericalGeodesyRhumbLine();

  // geometries
  _geometryTypes2D();
  _point();
  _lineString();
  _polygon();
  _multiPoint();
  _multiLineString();
  _multiPolygon();
  _geometryCollection();

  // geospatial features
  _feature();
  _featureCollection();

  // meta
  _temporalData();
  _geospatialExtents();

  // vector data
  _geoJsonWithDefaultCRS();
  _geoJsonWithAlternativeCRS();
  _wkt();
  _wkbSample1();
  _wkbSample2();

  // projections
  _wgs84ToWebMercatorViaPositions();
  _wgs84ToWebMercatorViaProjections();
  // see also separate file "geobase_with_proj4d_example.dart"

  // tiling schemes
  _webMercatorQuad();
  _globalGeodeticQuad();
}

void _intro() {
  // General purpose positions, series of positions and bounding boxes.

  // A position as a view on a coordinate array containing x and y.
  Position.view([708221.0, 5707225.0]);

  // The sample above shorted.
  [708221.0, 5707225.0].xy;

  // A bounding box.
  Box.view([70800.0, 5707200.0, 70900.0, 5707300.0]);

  // A series of positions from an array of position objects.
  PositionSeries.from(
    [
      [70800.0, 5707200.0].xy, // position 0 with (x, y) coordinate values
      [70850.0, 5707250.0].xy, // position 1 with (x, y) coordinate values
      [70900.0, 5707300.0].xy, // position 2 with (x, y) coordinate values
    ],
    type: Coords.xy,
  );

  // -------

  // *Geographic* and *projected* positions and bounding boxes.

  // A geographic position without and with an elevation.
  Geographic(lon: -0.0014, lat: 51.4778);
  Geographic(lon: -0.0014, lat: 51.4778, elev: 45.0);

  // A projected position without and with z.
  Projected(x: 708221.0, y: 5707225.0);
  Projected(x: 708221.0, y: 5707225.0, z: 45.0);

  // Geographic and projected bounding boxes.
  GeoBox(west: -20, south: 50, east: 20, north: 60);
  GeoBox(west: -20, south: 50, minElev: 100, east: 20, north: 60, maxElev: 200);
  ProjBox(minX: 10, minY: 10, maxX: 20, maxY: 20);

  // Positions and bounding boxes can be also built from an array or parsed.
  Geographic.build([-0.0014, 51.4778]);
  Geographic.parse('-0.0014,51.4778');
  Geographic.parse('-0.0014 51.4778', delimiter: ' ');
  Geographic.parseDms(lon: '0° 00′ 05″ W', lat: '51° 28′ 40″');
  GeoBox.build([-20, 50, 100, 20, 60, 200]);
  GeoBox.parse('-20,50,100,20,60,200');
  GeoBox.parseDms(west: '20°W', south: '50°N', east: '20°E', north: '60°N');

  // -------

  // Coordinates for *pixels* and *tiles* in tiling schemes.

  // Projected coordinates to represent *pixels* or *tiles* in tiling schemes.
  Scalable2i(zoom: 9, x: 23, y: 10);

  // -------

  // Spherical geodesy functions for great circle (shown) 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);

  // Destination point (10 km to bearing 61°): 51° 31.3′ N, 0° 07.5′ E
  greenwich.spherical.initialBearingTo(sydney);
  greenwich.spherical.finalBearingTo(sydney);

  // Destination point: 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);

  // -------

  // Geometry primitive and multi geometry objects.

  // A point with a 2D position.
  Point.build([30.0, 10.0]);

  // A line string (polyline) with three 2D positions.
  LineString.build([30, 10, 10, 30, 40, 40]);

  // A polygon with an exterior ring (and without any holes).
  Polygon.build([
    [30, 10, 40, 40, 20, 40, 10, 20, 30, 10]
  ]);

  // A polygon with an exterior ring and an interior ring as a hole.
  Polygon.build([
    [35, 10, 45, 45, 15, 40, 10, 20, 35, 10],
    [20, 30, 35, 35, 30, 20, 20, 30],
  ]);

  // A multi point with four points:
  MultiPoint.build([
    [10, 40],
    [40, 30],
    [20, 20],
    [30, 10]
  ]);

  // A multi line string with two line strings (polylines):
  MultiLineString.build([
    [10, 10, 20, 20, 10, 40],
    [40, 40, 30, 30, 40, 20, 30, 10]
  ]);

  // A multi polygon with two polygons both with an outer ring (without holes).
  MultiPolygon.build([
    [
      [30, 20, 45, 40, 10, 40, 30, 20],
    ],
    [
      [15, 5, 40, 10, 10, 20, 5, 10, 15, 5],
    ],
  ]);

  // A geometry collection with a point, a line string and a polygon.
  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],
    ])
  ]);

  // -------

  // To distinguish between arrays of different spatial dimensions you can use
  // `Coords` enum.
  LineString.build([30, 10, 10, 30, 40, 40]); // default type == Coords.xy
  LineString.build([30, 10, 10, 30, 40, 40], type: Coords.xy);
  LineString.build([30, 10, 5.5, 10, 30, 5.5, 40, 40, 5.5], type: Coords.xyz);

  // -------

  // 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);

  // -------

  // Features represent geospatial entities with properies and geometries.
  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',
    },
  );

  // The GeoJSON format is supported as text input and output for features.
  final feature = Feature.parse(
    '''
      { 
        "type": "Feature", 
        "id": "ROG", 
        "geometry": {
          "type": "Point", 
          "coordinates": [-0.0014, 51.4778, 45.0]
        }, 
        "properties": {
          "title": "Royal Observatory"
        }
      }
    ''',
    format: GeoJSON.feature,
  );
  print(feature.toText(format: GeoJSON.feature));
}

void _positionData() {
  // A position as a view on a coordinate array containing x and y.
  Position.view([708221.0, 5707225.0]);

  // A position as a view on a coordinate array containing x, y and z.
  Position.view([708221.0, 5707225.0, 45.0]);

  // A position as a view on a coordinate array containing x, y, z and m.
  Position.view([708221.0, 5707225.0, 45.0, 123.0]);

  // The samples above can be shorted using extension methods on `List<double>`.
  [708221.0, 5707225.0].xy;
  [708221.0, 5707225.0, 45.0].xyz;
  [708221.0, 5707225.0, 45.0, 123.0].xyzm;

  // There are also some other factory methods.
  Position.create(x: 708221.0, y: 5707225.0, z: 45.0, m: 123.0);
  Position.parse('708221.0,5707225.0,45.0,123.0');
  Position.parse('708221.0 5707225.0 45.0 123.0', delimiter: ' ');

  // -------

  // The same bounding box (limits on x and y) created with different factories.
  Box.view([70800.0, 5707200.0, 70900.0, 5707300.0]);
  Box.create(minX: 70800.0, minY: 5707200.0, maxX: 70900.0, maxY: 5707300.0);
  Box.parse('70800.0,5707200.0,70900.0,5707300.0');
  Box.parse('70800.0 5707200.0 70900.0 5707300.0', delimiter: ' ');
}

void _positionSeries() {
  // A position series from a flat coordinate value array.
  PositionSeries.view(
    [
      70800.0, 5707200.0, // (x, y) coordinate values for position 0
      70850.0, 5707250.0, // (x, y) coordinate values for position 1
      70900.0, 5707300.0, // (x, y) coordinate values for position 2
    ],
    type: Coords.xy,
  );

  // A position series from an array of position objects.
  PositionSeries.from(
    [
      [70800.0, 5707200.0].xy, // position 0 with (x, y) coordinate values
      [70850.0, 5707250.0].xy, // position 1 with (x, y) coordinate values
      [70900.0, 5707300.0].xy, // position 2 with (x, y) coordinate values
    ],
    type: Coords.xy,
  );

  // -------

  // A position series with three positions each with x and y coordinates.
  PositionSeries.view(
    [
      10.0, 11.0, // (x, y) for position 0
      20.0, 21.0, // (x, y) for position 1
      30.0, 31.0, // (x, y) for position 2
    ],
    type: Coords.xy,
  );

  // A shortcut to create a position series with three positions (with x and y).
  [
    10.0, 11.0, // (x, y) for position 0
    20.0, 21.0, // (x, y) for position 1
    30.0, 31.0, // (x, y) for position 2
  ].positions(Coords.xy);

  // A position series with three positions each with x, y and z coordinates.
  PositionSeries.view(
    [
      10.0, 11.0, 12.0, // (x, y, z) for position 0
      20.0, 21.0, 22.0, // (x, y, z) for position 1
      30.0, 31.0, 32.0, // (x, y, z) for position 2
    ],
    type: Coords.xyz,
  );
}

void _geographicCoordinates() {
  // A geographic position with longitude and latitude.
  Geographic(lon: -0.0014, lat: 51.4778);

  // A geographic position with longitude, latitude and elevation.
  Geographic(lon: -0.0014, lat: 51.4778, elev: 45.0);

  // A geographic position with longitude, latitude, elevation and measure.
  Geographic(lon: -0.0014, lat: 51.4778, elev: 45.0, m: 123.0);

  // The last sample also from a double list or text (order: lon, lat, elev, m).
  Geographic.build([-0.0014, 51.4778, 45.0, 123.0]);
  Geographic.parse('-0.0014,51.4778,45.0,123.0');
  Geographic.parse('-0.0014 51.4778 45.0 123.0', delimiter: ' ');

  // -------

  // A geographic bbox (-20 .. 20 in longitude, 50 .. 60 in latitude).
  GeoBox(west: -20, south: 50, east: 20, north: 60);

  // A geographic bbox with limits (100 .. 200) on the elevation coordinate too.
  GeoBox(west: -20, south: 50, minElev: 100, east: 20, north: 60, maxElev: 200);

  // The last sample also from a double list or text.
  GeoBox.build([-20, 50, 100, 20, 60, 200]);
  GeoBox.parse('-20,50,100,20,60,200');
}

void _geographicCoordinatesDMS() {
  // A geographic position can also be parsed from sexagesimal degrees (latitude
  // and longitude subdivided to degrees, minutes and seconds).

  // Decimal degrees (DD) with signed numeric degree values.
  Geographic.parseDms(lat: '51.4778', lon: '-0.0014');

  // Decimal degrees (DD) with degree and cardinal direction symbols (N/E/S/W).
  Geographic.parseDms(lat: '51.4778°N', lon: '0.0014°W');

  // Degrees and minutes (DM).
  Geographic.parseDms(lat: '51°28.668′N', lon: '0°00.084′W');

  // Degrees, minutes and seconds (DMS).
  Geographic.parseDms(lat: '51° 28′ 40″ N', lon: '0° 00′ 05″ W');

  // -------

  // Format geographic coordinates as string representations (DD, DM, DMS).

  const p = Geographic(lat: 51.4778, lon: -0.0014);

  // all three samples print decimal degrees: 51.4778°N 0.0014°W
  print(p.latLonDms(separator: ' '));
  print('${p.latDms()} ${p.lonDms()}');
  print('${Dms().lat(51.4778)} ${Dms().lon(-0.0014)}');

  // prints degrees and minutes: 51°28.668′N, 0°00.084′W
  const dm = Dms(type: DmsType.degMin, decimals: 3);
  print(p.latLonDms(format: dm));

  // prints degrees, minutes and seconds: 51° 28′ 40″ N, 0° 00′ 05″ W
  const dms = Dms.narrowSpace(type: DmsType.degMinSec);
  print(p.latLonDms(format: dms));

  // 51 degrees 28 minutes 40 seconds to N, 0 degrees 0 minutes 5 seconds to W
  const dmsTextual = Dms(
    type: DmsType.degMinSec,
    separator: ' ',
    decimals: 0,
    zeroPadMinSec: false,
    degree: ' degrees',
    prime: ' minutes',
    doublePrime: ' seconds to',
  );
  print(p.latLonDms(format: dmsTextual));

  // -------

  // Parsing and formatting is supported also for geographic bounding boxes.

  // Parses box from decimal degrees (DD) with cardinal direction symbols.
  final box =
      GeoBox.parseDms(west: '20°W', south: '50°N', east: '20°E', north: '60°N');

  // prints degrees and minutes: 20°0′W 50°0′N, 20°0′E 60°0′N
  const dm0 = Dms(type: DmsType.degMin, decimals: 0, zeroPadMinSec: false);
  print('${box.westDms(dm0)} ${box.southDms(dm0)}'
      ' ${box.eastDms(dm0)} ${box.northDms(dm0)}');
}

void _projectedCoordinates() {
  // A projected position with x and y.
  Projected(x: 708221.0, y: 5707225.0);

  // A projected position with x, y and z.
  Projected(x: 708221.0, y: 5707225.0, z: 45.0);

  // A projected position with x, y, z and m.
  Projected(x: 708221.0, y: 5707225.0, z: 45.0, m: 123.0);

  // The last sample also from a double list or text (order: x, y, z, m).
  Projected.build([708221.0, 5707225.0, 45.0, 123.0]);
  Projected.parse('708221.0,5707225.0,45.0,123.0');
  Projected.parse('708221.0 5707225.0 45.0 123.0', delimiter: ' ');

  // -------

  // A projected bbox with limits on x and y.
  ProjBox(minX: 10, minY: 10, maxX: 20, maxY: 20);

  // A projected bbox with limits on x, y and z.
  ProjBox(minX: 10, minY: 10, minZ: 10, maxX: 20, maxY: 20, maxZ: 20);

  // The last sample also from a double list or text.
  ProjBox.build([10, 10, 10, 20, 20, 20]);
  ProjBox.parse('10,10,10,20,20,20');
}

void _scalableCoordinates() {
  // A pixel with a zoom level (or LOD = level of detail) coordinates.
  const pixel = Scalable2i(zoom: 9, x: 23, y: 10);

  // Such coordinates can be scaled to other zoom levels.
  pixel.zoomIn(); // => Scalable2i(zoom: 10, x: 46, y: 20);
  pixel.zoomOut(); // => Scalable2i(zoom: 8, x: 11, y: 5);
  pixel.zoomTo(13); // => Scalable2i(zoom: 13, x: 368, y: 160));
}

void _sphericalGeodesyGreatCircle() {
  // sample geographic positions
  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');

  // decimal degrees (DD) and degrees-minutes (DM) formats
  const dd = Dms(decimals: 0);
  const dm = Dms.narrowSpace(type: DmsType.degMin, decimals: 1);

  // prints: 16988 km
  final distanceKm = greenwich.spherical.distanceTo(sydney) / 1000.0;
  print('${distanceKm.toStringAsFixed(0)} km');

  // prints (bearing varies along the great circle path): 61° -> 139°
  final initialBearing = greenwich.spherical.initialBearingTo(sydney);
  final finalBearing = greenwich.spherical.finalBearingTo(sydney);
  print('${dd.bearing(initialBearing)} -> ${dd.bearing(finalBearing)}');

  // prints: 51° 31.3′ N, 0° 07.5′ E
  final destPoint =
      greenwich.spherical.destinationPoint(distance: 10000, bearing: 61.0);
  print(destPoint.latLonDms(format: dm));

  // prints: 28° 34.0′ N, 104° 41.6′ E
  final midPoint = greenwich.spherical.midPointTo(sydney);
  print(midPoint.latLonDms(format: dm));

  // prints 10 intermediate points, like fraction 0.6: 16° 14.5′ N, 114° 29.3′ E
  for (var fr = 0.0; fr < 1.0; fr += 0.1) {
    final ip = greenwich.spherical.intermediatePointTo(sydney, fraction: fr);
    print('${fr.toStringAsFixed(1)}: ${ip.latLonDms(format: dm)}');
  }

  // prints: 0° 00.0′ N, 125° 19.0′ E
  final intersection = greenwich.spherical.intersectionWith(
    bearing: 61.0,
    other: const Geographic(lat: 0.0, lon: 179.0),
    otherBearing: 270.0,
  );
  if (intersection != null) {
    print(intersection.latLonDms(format: dm));
  }
}

void _sphericalGeodesyRhumbLine() {
  // sample geographic positions
  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');

  // decimal degrees (DD) and degrees-minutes (DM) formats
  const dd = Dms(decimals: 0);
  const dm = Dms.narrowSpace(type: DmsType.degMin, decimals: 1);

  // prints: 17670 km
  final distanceKm = greenwich.rhumb.distanceTo(sydney) / 1000.0;
  print('${distanceKm.toStringAsFixed(0)} km');

  // prints (bearing remains the same along the rhumb line path): 122° -> 122°
  final initialBearing = greenwich.rhumb.initialBearingTo(sydney);
  final finalBearing = greenwich.rhumb.finalBearingTo(sydney);
  print('${dd.bearing(initialBearing)} -> ${dd.bearing(finalBearing)}');

  // prints: 51° 25.8′ N, 0° 07.3′ E
  final destPoint =
      greenwich.spherical.destinationPoint(distance: 10000, bearing: 122.0);
  print(destPoint.latLonDms(format: dm));

  // prints: 8° 48.3′ N, 80° 44.0′ E
  final midPoint = greenwich.rhumb.midPointTo(sydney);
  print(midPoint.latLonDms(format: dm));
}

void _geometryTypes2D() {
  // point (with a position)
  Point.build([30.0, 10.0]);

  // line string (with a chain)
  LineString.build([30, 10, 10, 30, 40, 40]);

  // polygon (with an exterior ring)
  Polygon.build([
    [30, 10, 40, 40, 20, 40, 10, 20, 30, 10],
  ]);

  // polygon (with an exterior ring and one interior ring as a hole)
  Polygon.build(
    [
      [35, 10, 45, 45, 15, 40, 10, 20, 35, 10],
      [20, 30, 35, 35, 30, 20, 20, 30],
    ],
  );

  // multi point (with four points)
  MultiPoint.build(
    [
      [10, 40],
      [40, 30],
      [20, 20],
      [30, 10],
    ],
  );

  // multi line string (with two line strings)
  MultiLineString.build(
    [
      [10, 10, 20, 20, 10, 40],
      [40, 40, 30, 30, 40, 20, 30, 10],
    ],
  );

  // multi polygon (with two polygons)
  MultiPolygon.build(
    [
      [
        [30, 20, 45, 40, 10, 40, 30, 20],
      ],
      [
        [15, 5, 40, 10, 10, 20, 5, 10, 15, 5],
      ],
    ],
  );

  // multi polygon (with one polygon without a hole, and another 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],
      ],
    ],
  );

  // geometry collection (with point, line string and polygon geometries)
  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],
    ])
  ]);
}

/*
void _geometryTypesOneliners() {
  // same samples as in "_geometryTypes" but formatted to fit README table

  // point (with a position)
  Point.build([30.0, 10.0]);

  // line string (with a chain)
  LineString.build([30, 10, 10, 30, 40, 40]);

  // polygon (with an exterior ring)
  Polygon.build([[30, 10, 40, 40, 20, 40, 10, 20, 30, 10]]);

  // polygon (with an exterior ring and one interior ring as a hole)
  Polygon.build([[35, 10, 45, 45, 15, 40, 10, 20, 35, 10], [20, 30, 35, 35, 30, 20, 20, 30]]);

  // multi point (with four points)
  MultiPoint.build([[10, 40], [40, 30], [20, 20], [30, 10]]);

  // multi line string (with two line strings)
  MultiLineString.build([[10, 10, 20, 20, 10, 40], [40, 40, 30, 30, 40, 20, 30, 10]]);

  // multi polygon (with two polygons)
  MultiPolygon.build([[[30, 20, 45, 40, 10, 40, 30, 20]], [[15, 5, 40, 10, 10, 20, 5, 10, 15, 5]]]);

  // multi polygon (with one polygon without a hole, and another 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]]]);

  // geometry collection (with point, line string and polygon geometries)
  GeometryCollection([Point.build([30.0, 10.0]), LineString.build([10, 10, 20, 20, 10, 40], type: Coords.xy), Polygon.build([[40, 40, 20, 45, 45, 30, 40, 40]])]);
}
*/

void _point() {}

void _lineString() {
  // A line string with 3 points (2D coordinates with x and y).
  LineString.from([
    [30.0, 10.0].xy, // xy => Position.view()
    [10.0, 30.0].xy,
    [40.0, 40.0].xy,
  ]);

  // A line string with 3 points (3D coordinates with x, y and z).
  LineString.from([
    Geographic(lon: 30, lat: 10, elev: 5.5), // x = lon, y = lat, z = elev
    Geographic(lon: 10, lat: 30, elev: 5.5),
    Geographic(lon: 40, lat: 40, elev: 5.5),
  ]);

  // A line string with 3 points (3D and measured coordinates: x, y, z and m).
  LineString.from([
    Projected(x: 30, y: 10, z: 5.5, m: 3.1),
    Projected(x: 10, y: 30, z: 5.5, m: 3.2),
    Projected(x: 40, y: 40, z: 5.5, m: 3.3),
  ]);
}

void _polygon() {}

void _multiPoint() {}

void _multiLineString() {}

void _multiPolygon() {}

void _geometryCollection() {}

void _feature() {
  // A geospatial feature with id, a point geometry and properties.
  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',
      'place': 'Greenwich',
      'city': 'London',
      'isMuseum': true,
      'measure': 5.79,
    },
  );
}

void _featureCollection() {
  // 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',
        'place': 'Greenwich',
        'city': 'London',
        'isMuseum': true,
        'measure': 5.79,
      },
    ),
    Feature(
      id: 'TB',
      // a point geometry with a position (lon, lat)
      geometry: Point.build([-0.075406, 51.5055]),
      properties: {
        'title': 'Tower Bridge',
        'city': 'London',
        'built': 1886,
      },
    ),
  ]);
}

void _geoJsonWithDefaultCRS() {
  // build a LineString sample geometry
  final lineString = LineString.build(
    [-1.1, -1.1, 2.1, -2.5, 3.5, -3.49],
    type: Coords.xy,
    bounds: [-1.1, -3.49, 3.5, -1.1].box,
  );

  // ... and print it as GeoJSON text:
  //   {
  //     "type":"LineString",
  //     "bbox":[-1.1,-3.49,3.5,-1.1],
  //     "coordinates":[[-1.1,-1.1],[2.1,-2.5],[3.5,-3.49]]
  //   }
  print(lineString.toText(format: GeoJSON.geometry));

  // GeoJSON representation for other geometries, features and feature
  // collections can be produced with `toText` methdod also.

  // here a Feature is printed as GeoJSON text (with 3 decimals on doubles):
  //   {
  //     "type":"Feature",
  //     "id":"TB",
  //     "geometry":{"type":"Point","coordinates":[-0.075,51.505]},
  //     "properties":{"title":"Tower Bridge","city":"London","built":1886}
  //   }
  final feature = Feature(
    id: 'TB',
    geometry: Point.build([-0.075406, 51.5055]),
    properties: {
      'title': 'Tower Bridge',
      'city': 'London',
      'built': 1886,
    },
  );
  print(feature.toText(format: GeoJSON.feature, decimals: 3));

  // -------

  // sample GeoJSON text representation (a feature collection with two features)
  const sample = '''
    {
      "type": "FeatureCollection",
      "features": [
        {
          "type": "Feature",
          "id": "ROG",
          "geometry": {
            "type": "Point",
            "coordinates": [-0.0014, 51.4778, 45.0]  
          },
          "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 of the GeoJSON format
  final collection = FeatureCollection.parse(sample, format: GeoJSON.feature);

  // loop through features and print id, geometry and properties for each
  for (final feature in collection.features) {
    print('Feature with id: ${feature.id}');
    print('  geometry: ${feature.geometry}');
    print('  properties:');
    for (final key in feature.properties.keys) {
      print('    $key: ${feature.properties[key]}');
    }
  }
}

void _geoJsonWithAlternativeCRS() {
  // CRS for geographic coordinates with latitude before longitude in GeoJSON.
  const epsg4326 = CoordRefSys.EPSG_4326;

  // Read GeoJSON content with coordinate order: longitude, latitude, elevation.
  final point1 = Point.parse(
    '{"type": "Point", "coordinates": [-0.0014, 51.4778, 45.0]}',
    // no CRS must be specified for the default coordinate reference system:
    // `CoordRefSys.CRS84` or `http://www.opengis.net/def/crs/OGC/1.3/CRS84`
  );
  final pos1 = Geographic.from(point1.position);
  // prints: Point1: lon: 0.0014°W lat: 51.4778°N
  print('Point1: lon: ${pos1.lonDms()} lat: ${pos1.latDms()}');

  // Read GeoJSON content with coordinate order: latitude, longitude, elevation.
  final point2 = Point.parse(
    '{"type": "Point", "coordinates": [51.4778, -0.0014, 45.0]}',
    crs: epsg4326, // CRS must be explicitely specified
  );
  final pos2 = Geographic.from(point2.position);
  // prints: Point2: lon: 0.0014°W lat: 51.4778°N
  print('Point2: lon: ${pos2.lonDms()} lat: ${pos2.latDms()}');

  // Both `point1` and `point2` store coordinates internally in this order:
  // longitude, latitude, elevation.

  // Writing GeoJSON without crs information expects longitude-latitude order.
  // Prints: {"type":"Point","coordinates":[-0.0014,51.4778,45.0]}
  print(point2.toText(format: GeoJSON.geometry));

  // Writing with crs (EPSG:4326) results in latitude-longitude order.
  // Prints: {"type":"Point","coordinates":[51.4778,-0.0014,45.0]}
  print(point2.toText(format: GeoJSON.geometry, crs: epsg4326));
}

void _wkt() {
  // parse a Point geometry from WKT text
  final point = Point.parse(
    'POINT ZM(10.123 20.25 -30.95 -1.999)',
    format: WKT.geometry,
  );

  // format it (back) as WKT text that is printed:
  //    POINT ZM(10.123 20.25 -30.95 -1.999)
  print(point.toText(format: WKT.geometry));

  // -------

  // if geometry type is not known when parsing text from external datasources,
  // you can use `GeometryBuilder` to parse geometries of any type

  const geometriesWkt = [
    'POINT Z(10.123 20.25 -30.95)',
    'LINESTRING(-1.1 -1.1, 2.1 -2.5, 3.5 -3.49)',
  ];
  for (final geomWkt in geometriesWkt) {
    // parse geometry (Point and LineString inherits from Geometry)
    final Geometry geom = GeometryBuilder.parse(geomWkt, format: WKT.geometry);

    if (geom is Point) {
      // do something with point geometry
    } else if (geom is LineString) {
      // do something with line string geometry
    }
  }

  // -------

  // It's possible to encode geometry data as WKT text also without creating
  // geometry objects first. However this requires accessing an encoder instance
  // from the WKT format, and then writing content to that encoder.

  // geometry text format encoder for WKT
  const format = WKT.geometry;
  final encoder = format.encoder();

  // prints:
  //    POINT ZM(10.123 20.25 -30.95 -1.999)
  encoder.writer.point(
    [10.123, 20.25, -30.95, -1.999].xyzm,
  );
  print(encoder.toText());
}

void _wkbSample1() {
  // geometry binary format encoder for WKB
  const format = WKB.geometry;
  final encoder = format.encoder();

  // write geometries (here only point) to content writer of the encoder
  encoder.writer.point(
    [10.123, 20.25, -30.95, -1.999].xyzm,
  );

  // get encoded bytes (Uint8List) and Base64 encoded text (String)
  final wkbBytes = encoder.toBytes();
  final wkbBytesAsBase64 = encoder.toText();

  // prints (point encoded to WKB binary data, formatted as Base64 text):
  //    AAAAC7lAJD752yLQ5UA0QAAAAAAAwD7zMzMzMzO///vnbItDlg==
  print(wkbBytesAsBase64);

  // next decode this WKB binary data and use WKT text format encoder as target

  // geometry text format encoder for WKT
  final wktEncoder = WKT.geometry.encoder();

  // geometry binary format decoder for WKB
  // (with content writer of the WKT encoder set as a target for decoding)
  final decoder = WKB.geometry.decoder(wktEncoder.writer);

  // now decode those WKB bytes (Uint8List) created already at the start
  decoder.decodeBytes(wkbBytes);

  // finally print WKT text:
  //    POINT ZM(10.123 20.25 -30.95 -1.999)
  print(wktEncoder.toText());
}

/// The previous sample ("_wkbSample1") using geometry model objects.
void _wkbSample2() {
  // create a Point object
  final point = Point.build([10.123, 20.25, -30.95, -1.999]);

  // get encoded bytes (Uint8List)
  final wkbBytes = point.toBytes(format: WKB.geometry);

  // at this point our WKB bytes could be sent to another system...

  // then create a Point object, but now decoding it from WKB bytes
  final pointDecoded = Point.decode(wkbBytes, format: WKB.geometry);

  // finally print WKT text:
  //    POINT ZM(10.123 20.25 -30.95 -1.999)
  print(pointDecoded.toText(format: WKT.geometry));

  // -------

  // or as a bonus of this solution it's as easy to print it as GeoJSON text too
  //    {"type":"Point","coordinates":[10.123,20.25,-30.95,-1.999]}
  print(pointDecoded.toText(format: GeoJSON.geometry));

  // great, but, we just forgot that GeoJSON should not contain m coordinates...
  //    {"type":"Point","coordinates":[10.123,20.25,-30.95]}
  print(
    pointDecoded.toText(
      format: GeoJSON.geometryFormat(conf: GeoJsonConf(ignoreMeasured: true)),
    ),
  );
}

void _temporalData() {
  // Instants can be created from `DateTime` or parsed from text.
  Instant(DateTime.utc(2020, 10, 31, 09, 30));
  Instant.parse('2020-10-31 09:30Z');

  // Intervals (open-started, open-ended, closed).
  Interval.openStart(DateTime.utc(2020, 10, 31));
  Interval.openEnd(DateTime.utc(2020, 10, 01));
  Interval.closed(DateTime.utc(2020, 10, 01), DateTime.utc(2020, 10, 31));

  // Same intervals parsed (by the "start/end" format, ".." for open limits).
  Interval.parse('../2020-10-31');
  Interval.parse('2020-10-01/..');
  Interval.parse('2020-10-01/2020-10-31');
}

void _geospatialExtents() {
  // An extent with spatial (WGS 84 longitude-latitude) and temporal parts.
  GeoExtent.single(
    crs: CoordRefSys.CRS84,
    bbox: GeoBox(west: -20.0, south: 50.0, east: 20.0, north: 60.0),
    interval: Interval.parse('../2020-10-31'),
  );

  // An extent with multiple spatial bounds and temporal interval segments.
  GeoExtent.multi(
    crs: CoordRefSys.CRS84,
    boxes: [
      GeoBox(west: -20.0, south: 50.0, east: 20.0, north: 60.0),
      GeoBox(west: 40.0, south: 50.0, east: 60.0, north: 60.0),
    ],
    intervals: [
      Interval.parse('2020-10-01/2020-10-05'),
      Interval.parse('2020-10-27/2020-10-31'),
    ],
  );
}

void _wgs84ToWebMercatorViaPositions() {
  // Built-in coordinate projections (currently only between WGS 84 and
  // Web Mercator)

  // Sample point as geographic coordinates.
  const geographic = Geographic(lon: -0.0014, lat: 51.4778);

  // Geographic (WGS 84 longitude-latitude) to Projected (WGS 84 Web Mercator).
  final forward = WGS84.webMercator.forward;
  final projected = geographic.project(forward);

  // Projected (WGS 84 Web Mercator) to Geographic (WGS 84 longitude-latitude).
  final inverse = WGS84.webMercator.inverse;
  final unprojected = projected.project(inverse);

  print('${unprojected.toText(decimals: 5)}'
      ' <=> ${projected.toText(decimals: 5)}');
}

void _wgs84ToWebMercatorViaProjections() {
  // Built-in coordinate projections (currently only between WGS 84 and
  // Web Mercator)

  // Geographic (WGS 84 longitude-latitude) to Projected (WGS 84 Web Mercator)
  final forward = WGS84.webMercator.forward;
  final projected = forward.project(
    const Geographic(lon: -0.0014, lat: 51.4778),
    to: Projected.create,
  );

  // Projected (WGS 84 Web Mercator) to Geographic (WGS 84 longitude-latitude)
  final inverse = WGS84.webMercator.inverse;
  final unprojected = inverse.project(
    projected,
    to: Geographic.create,
  );

  print('${unprojected.toText(decimals: 5)}'
      ' <=> ${projected.toText(decimals: 5)}');
}

/// "Web Mercator Quad" tile matrix set.
///
/// [OGC Two Dimensional Tile Matrix Set](https://docs.opengeospatial.org/is/17-083r2/17-083r2.html):
/// "Level 0 allows representing most of the world (limited to latitudes
/// between approximately ±85 degrees) in a single tile of 256x256 pixels
/// (Mercator projection cannot cover the whole world because mathematically
/// the poles are at infinity). The next level represents most of the world
/// in 2x2 tiles of 256x256 pixels and so on in powers of 2. Mercator
/// projection distorts the pixel size closer to the poles. The pixel sizes
/// provided here are only valid next to the equator."
///
/// Using "Web Mercator Quad" involves following coordinates:
/// * *position*: geographic coordinates (longitude, latitude)
/// * *world*: a position projected to the pixel space of the map at level 0
/// * *pixel*: pixel coordinates (x, y) in the pixel space of the map at zoom
/// * *tile*: tile coordinates (x, y) in the tile matrix at zoom
void _webMercatorQuad() {
  // "WebMercatorQuad" tile matrix set with 256 x 256 pixel tiles and with
  // "top-left" origin for the tile matrix and map pixel space
  const quad = WebMercatorQuad.epsg3857();

  // source position as geographic coordinates
  const position = Geographic(lon: -0.0014, lat: 51.4778);

  // get world, tile and pixel coordinates for a geographic position
  print(
    quad.positionToWorld(position).toText(decimals: 6),
  ); // ~ x=127.999004 y=85.160341
  print(quad.positionToTile(position, zoom: 2)); // zoom=2 x=1 y=1
  print(quad.positionToPixel(position, zoom: 2)); // zoom=2 x=511 y=340
  print(quad.positionToPixel(position, zoom: 4)); // zoom=4 x=2047 y=1362

  // world coordinates can be instantiated as projected coordinates
  // x range: (0.0, 256.0) / y range: (0.0, 256.0)
  const world = Projected(x: 127.99900444444444, y: 85.16034098329446);

  // from world coordinates to tile and pixel coordinates
  print(quad.worldToTile(world, zoom: 2)); // zoom=2 x=1 y=1
  print(quad.worldToPixel(world, zoom: 2)); // zoom=2 x=511 y=340
  print(quad.worldToPixel(world, zoom: 4)); // zoom=4 x=2047 y=1362

  // tile and pixel coordinates with integer values can be defined too
  const tile = Scalable2i(zoom: 2, x: 1, y: 1);
  const pixel = Scalable2i(zoom: 2, x: 511, y: 340);

  // tile and pixel coordinates can be zoomed (scaled to other level of details)
  print(pixel.zoomIn()); // zoom=3 x=1022 y=680
  print(pixel.zoomOut()); // zoom=1 x=255 y=170

  // get tile bounds and pixel position (accucy lost) as geographic coordinates
  print(quad.tileToBounds(tile)); // west: -90 south: 0 east: 0 north: 66.51326
  print(quad.pixelToPosition(pixel)); // longitude: -0.17578 latitude: 51.50874

  // world coordinates returns geographic positions still accurately
  print(quad.worldToPosition(world)); // longitude: -0.00140 latitude: 51.47780

  // aligned points (world, pixel and position coordinates) inside tile or edges
  print(quad.tileToWorld(tile, align: Aligned.northWest));
  print(quad.tileToPixel(tile, align: Aligned.center));
  print(quad.tileToPosition(tile, align: Aligned.center));
  print(quad.tileToPosition(tile, align: Aligned.southEast));

  // get zoomed tile at the center of a source tile
  final centerOfTile2 = quad.tileToWorld(tile, align: Aligned.center);
  final tile7 = quad.worldToTile(centerOfTile2, zoom: 7);
  print('tile at zoom 2: $tile => center of tile: $centerOfTile2 '
      '=> tile at zoom 7: $tile7');

  // a quad key is a string identifier for tiles
  print(quad.tileToQuadKey(tile)); // "03"
  print(quad.quadKeyToTile('03')); // zoom=2 x=1 y=1
  print(quad.quadKeyToTile('0321')); // zoom=4 x=5 y=6

  // tile size and map bounds can be checked dynamically
  print(quad.tileSize); // 256
  print(quad.mapBounds()); // ~ west: -180 south: -85.05 east: 180 north: 85.05

  // matrix width and height tells number of tiles in a given zoom level
  print('${quad.matrixWidth(2)} x ${quad.matrixHeight(2)}'); // 4 x 4
  print('${quad.matrixWidth(10)} x ${quad.matrixHeight(10)}'); // 1024 x 1024

  // map width and height tells number of pixels in a given zoom level
  print('${quad.mapWidth(2)} x ${quad.mapHeight(2)}'); // 1024 x 1024
  print('${quad.mapWidth(10)} x ${quad.mapHeight(10)}'); // 262144 x 262144

  // ground resolutions and scale denominator for zoom level 10 at the Equator
  print(quad.tileGroundResolution(10)); // ~ 39135.76 (meters)
  print(quad.pixelGroundResolution(10)); // ~ 152.87 (meters)
  print(quad.scaleDenominator(10)); // ~ 545978.77

  // inverse: zoom from ground resolution and scale denominator
  print(quad.zoomFromPixelGroundResolution(152.87)); // ~ 10.0 (double value)
  print(quad.zoomFromScaleDenominator(545978.77)); // ~ 10.0 (double value)

  // ground resolutions and scale denominator for zoom level 10 at lat 51.4778
  print(quad.pixelGroundResolutionAt(latitude: 51.4778, zoom: 10)); // ~ 95.21
  print(quad.scaleDenominatorAt(latitude: 51.4778, zoom: 10)); // ~ 340045.31

  // inverse: zoom from ground resolution and scale denominator at lat 51.4778
  print(
    quad.zoomFromPixelGroundResolutionAt(
      latitude: 51.4778,
      resolution: 95.21,
    ),
  ); // ~ 10.0 (double value)
  print(
    quad.zoomFromScaleDenominatorAt(
      latitude: 51.4778,
      denominator: 340045.31,
    ),
  ); // ~ 10.0 (double value)
}

/// "Global Geodetic Quad" tile matrix set ("World CRS84 Quad" for WGS 84).
///
/// Tiles are defined in the Equirectangular Plate Carrée projection in the
/// CRS84 coordinate reference system (longitude, latitude) for the whole
/// world. At the zoom level 0 the world is covered by two tiles (tile matrix
/// width is 2 and matrix height is 1). The western tile (x=0, y=0) is for the
/// negative longitudes and the eastern tile (x=1, y=0) for the positive
/// longitudes.
///
/// Using "Global Geodetic Quad" involves following coordinates:
/// * *position*: geographic coordinates (longitude, latitude)
/// * *world*: a position scaled to the pixel space of the map at level 0
/// * *pixel*: pixel coordinates (x, y) in the pixel space of the map at zoom
/// * *tile*: tile coordinates (x, y) in the tile matrix at zoom
void _globalGeodeticQuad() {
  // "World CRS 84" tile matrix set with 256 x 256 pixel tiles and with
  // "top-left" origin for the tile matrix and map pixel space
  const quad = GlobalGeodeticQuad.worldCrs84();

  // source position as geographic coordinates
  const position = Geographic(lon: -0.0014, lat: 51.4778);

  // get world, tile and pixel coordinates for a geographic position
  print(
    quad.positionToWorld(position).toText(decimals: 6),
  ); // ~ x=255.998009 y=54.787129
  print(quad.positionToTile(position, zoom: 2)); // zoom=2 x=3 y=0
  print(quad.positionToPixel(position, zoom: 2)); // zoom=2 x=1023 y=219
  print(quad.positionToPixel(position, zoom: 4)); // zoom=4 x=4095 y=876

  // world coordinates can be instantiated as projected coordinates
  // x range: (0.0, 512.0) / y range: (0.0, 256.0)
  const world = Projected(x: 255.99800888888888, y: 54.78712888888889);

  // from world coordinates to tile and pixel coordinates
  print(quad.worldToTile(world, zoom: 2)); // zoom=2 x=3 y=0
  print(quad.worldToPixel(world, zoom: 2)); // zoom=2 x=1023 y=219
  print(quad.worldToPixel(world, zoom: 4)); //  zoom=4 x=4095 y=876

  // tile and pixel coordinates with integer values can be defined too
  const tile = Scalable2i(zoom: 2, x: 3, y: 0);
  const pixel = Scalable2i(zoom: 2, x: 1023, y: 219);

  // get tile bounds and pixel position (accucy lost) as geographic coordinates
  print(quad.tileToBounds(tile)); // west: -45 south: 45 east: 0 north: 90
  print(quad.pixelToPosition(pixel)); // longitude: -0.08789 latitude: 51.41602

  // world coordinates returns geographic positions still accurately
  print(quad.worldToPosition(world)); // longitude: -0.00140 latitude: 51.4778

  // tile size and map bounds can be checked dynamically
  print(quad.tileSize); // 256
  print(quad.mapBounds()); // west: -180 south: -90 east: 180 north: 90

  // matrix width and height tells number of tiles in a given zoom level
  print('${quad.matrixWidth(2)} x ${quad.matrixHeight(2)}'); // 8 x 4
  print('${quad.matrixWidth(10)} x ${quad.matrixHeight(10)}'); // 2048 x 1024

  // map width and height tells number of pixels in a given zoom level
  print('${quad.mapWidth(2)} x ${quad.mapHeight(2)}'); // 2048 x 1024
  print('${quad.mapWidth(10)} x ${quad.mapHeight(10)}'); // 524288 x 262144

  // arc resolutions and scale denominator for zoom level 10 at the Equator
  print(quad.tileArcResolution(10)); // ~ 0.175781 (° degrees)
  print(quad.pixelArcResolution(10)); // ~ 0.000686646 (° degrees)
  print(quad.scaleDenominator(10)); // ~ 272989.39

  // inverse: zoom from scale denominator at the Equator
  print(quad.zoomFromScaleDenominator(272989.39)); // ~ 10.0 (double value)
}
27
likes
140
points
1.98k
downloads

Publisher

verified publishernavibyte.com

Weekly Downloads

Geospatial data, spherical geodesy, projections, tiling schemes, and vector formats (GeoJSON, WKT, WKB).

Homepage
Repository (GitHub)

Documentation

API reference

License

BSD-3-Clause (license)

Dependencies

equatable, meta, proj4dart

More

Packages that depend on geobase