GeoEngine
GeoEngine is a comprehensive Dart library designed for geospatial and geomatic calculations. It provides a wide range of functionalities including distance calculations, coordinate conversions, geocoding, polygon operations, geodetic network analysis, and much more. Whether you are a GIS professional, a geomatics engineer, or a developer working on geospatial applications, GeoEngine is the ultimate toolkit for all your geospatial needs.
Getting Started
Installation
Add this to your package's pubspec.yaml
file:
dependencies:
geoengine: any
Usage
Here's a simple example to calculate the distance between two geographic coordinates:
import 'package:geoengine/geoengine.dart';
void main() {
var point1 = LatLng(37.7749, -122.4194);
var point2 = LatLng(34.0522, -118.2437);
var distance = Distance.haversine(point1, point2);
print('Distance between points is: ${distance.valueSI} meters');
}
Features
Core Calculations
Distance and Bearings
These are ported implementations of the java codes provided by Movable Type Scripts. This page presents a variety of calculations for latitude/longitude points, with the formulas and code fragments for implementing them.
- Distance Calculation: Calculate the distance between two geographic coordinates using various algorithms like Haversine, Vincenty, and Great Circle.
var point1 = LatLng(dms2Degree(50, 03, 59), dms2Degree(-5, 42, 53));
var point2 = LatLng(dms2Degree(58, 38, 38), dms2Degree(-3, 04, 12));
print('Distance (Haversine): ${point1.distanceTo(point2, method: DistanceMethod.haversine)!.valueInUnits(LengthUnits.kilometers)} km');
print('Distance (Great Circle): ${point1.distanceTo(point2, method: DistanceMethod.greatCircle)!.valueInUnits(LengthUnits.kilometers)} km');
print('Distance (Vincenty): ${point1.distanceTo(point2, method: DistanceMethod.vincenty)!.valueInUnits(LengthUnits.kilometers)} km');
// Distance (Haversine): 968.8535467131387 km
// Distance (Great Circle): 968.8535467131394 km
// Distance (Vincenty): 969.9329875845247 km
- Bearing Calculation: Calculate the initial and final bearing between two points on the Earth's surface.
var point1 = LatLng(dms2Degree(50, 03, 59), dms2Degree(-5, 42, 53));
var point2 = LatLng(dms2Degree(58, 38, 38), dms2Degree(-3, 04, 12));
print('Initial Bearing: ${point1.initialBearingTo(point2)}');
print('Final Bearing: ${point1.finalBearingTo(point2)}');
print('Mid Point: ${point1.midPointTo(point2)}');
// Initial Bearing: 9.119818104504077° or 0.15917085310658177 rad or 009° 07' 11.34518"
// Final Bearing: 11.275201271425715° or 0.19678938601142623 rad or 011° 16' 30.72458"
// Mid Point: 054° 21' 44.233" N, 004° 31' 50.421"
- Destination Point: Given a start point, initial bearing, and distance, this will calculate the destination point and final bearing travelling along a (shortest distance) great circle arc.
var startPoint = LatLng(53.3206, -1.7297); // 53°19′14″N, 001°43′47″W
double bearing = 96.022222; // 096°01′18″
double distance = 124800; // 124.8 km
LatLng destinationPoint = startPoint.destinationPoint(distance, bearing);
var finalBearing = startPoint.finalBearingTo(destinationPoint);
print('Destination point: $destinationPoint');
print('Final bearing: $finalBearing');
// Destination point: 053° 11' 17.891" N, 000° 07' 59.875" E
// Final bearing: 97.51509150337512° or 1.7019594171174142 rad or 097° 30' 54.32941"
- Interception: Intersection of two paths given start points and bearings This is a rather more complex calculation than most others on this page, but I've been asked for it a number of times. This comes from Ed William’s aviation formulary.
var point1 = LatLng(51.8853, 0.2545);
var bearing1 = 108.55;
var point2 = LatLng(49.0034, 2.5735);
var bearing2 = 32.44;
var intercept = LatLng.intersectionPoint(point1, bearing1, point2, bearing2)!;
print('Interception Point: $intercept');
// Interception Point: 050° 54' 27.387" N, 004° 30' 30.869" E
- Rhumb line: A ‘rhumb line’ (or loxodrome) is a path of constant bearing, which crosses all meridians at the same angle.
Sailors used to (and sometimes still) navigate along rhumb lines since it is easier to follow a constant compass bearing than to be continually adjusting the bearing, as is needed to follow a great circle. Rhumb lines are straight lines on a Mercator Projection map (also helpful for navigation).
var startPoint = LatLng(50.3667, -4.1340); // 50 21 59N, 004 08 02W
var endPoint = LatLng(42.3511, -71.0408); // 42 21 04N, 071 02 27W
var rhumbDist = startPoint.rhumbLineDistance(endPoint);
Bearing rhumbBearing = startPoint.rhumbLineBearing(endPoint);
LatLng rhumbMid = startPoint.rhumbMidpoint(endPoint);
print('Rhumb distance: ${rhumbDist.valueInUnits(LengthUnits.kilometers)} km');
print('Rhumb bearing: $rhumbBearing');
print('Rhumb midpoint: $rhumbMid');
// Rhumb distance: 5197.982109842136 km
// Rhumb bearing: Bearing: 256.66558069454646° or 4.479659459662955 rad or 256° 39' 56.09050"
// Rhumb midpoint: 047° 50' 9.060" N, 038° 13' 28.378" W
Given a start point and a distance d along constant bearing θ, this will calculate the destination point. If you maintain a constant bearing along a rhumb line, you will gradually spiral in towards one of the poles.
var sPt = LatLng(dms2Degree(51, 07, 32), dms2Degree(1, 20, 17));
var dist = 40230;
var bearing = dms2Degree(116, 38, 10);
print('Rhumb Destination: ${sPt.rhumbDestinationPoint(dist, bearing)}');
// Rhumb Destination: 050° 57' 48.074" N, 001° 51' 8.774" E
- Geodesic Calculations: Find the shortest path between two points on the Earth's surface, taking into account the Earth's curvature which uses the Vincenty approach.
Coordinate Systems
Coordinate Systems
- Coordinate Conversion: Convert between different coordinate systems, such as latitude/longitude to UTM or MGRS.
Get the UTM zone number and letter
var u = UTMZones();
var uZone = u.getZone(latitude: 6.5655, longitude: -1.5646);
print(uZone); // 30P
print(u.getHemisphere(uZone)); // N
print(u.getLatZone(6.5655)); // P
Parse MGRS coordinates
print(MGRS.parse('31U DQ 48251 11932')); // 31U DQ 48251 11932
print(MGRS.parse('31UDQ4825111932')); // 31U DQ 48251 11932
Coordinate conversions
var ll = LatLng(6.5655, -1.5646);
print(ll.toMGRS());
print(ll.toUTM());
// 30N XN 58699 25944
// 30 N 658699.0 725944.0 0.0
print('');
var utm = UTM.fromMGRS(ll.toMGRS());
print(utm);
print(utm.toLatLng());
print(utm.toMGRS());
// 30 N 658699.0 725944.0 0.0
// 006° 33' 55.795" N, 001° 33' 52.586" W
// 30N XN 58699 25944
print('');
var mgrs = MGRS.parse(ll.toMGRS());
print(mgrs.toLatLng());
print(mgrs.toUTM());
print(mgrs);
// 006° 33' 55.795" N, 001° 33' 52.586" W
// 30 N 658699.0 725944.0
// 30N XN 58699 25944
- Datum Transformations: Transform coordinates between different geodetic datums.
final LatLng pp = LatLng(6.65412, -1.54651, 200);
CoordinateConversion transCoordinate = CoordinateConversion();
Projection sourceProjection = Projection.get('EPSG:4326')!; // Geodetic
// Add a new CRS from WKT
Projection targetProjection = Projection.parse(
'PROJCS["Accra / Ghana National Grid",GEOGCS["Accra",DATUM["Accra",SPHEROID["War Office",6378300,296,AUTHORITY["EPSG","7029"]],TOWGS84[-199,32,322,0,0,0,0],AUTHORITY["EPSG","6168"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4168"]],PROJECTION["Transverse_Mercator"],PARAMETER["latitude_of_origin",4.666666666666667],PARAMETER["central_meridian",-1],PARAMETER["scale_factor",0.99975],PARAMETER["false_easting",900000],PARAMETER["false_northing",0],UNIT["Gold Coast foot",0.3047997101815088,AUTHORITY["EPSG","9094"]],AXIS["Easting",EAST],AXIS["Northing",NORTH],AUTHORITY["EPSG","2136"]]');
var res = transCoordinate.convert(
point: pp,
projSrc: sourceProjection,
projDst: targetProjection,
conversion: ConversionType.geodeticToGeodetic, // Geodetic to Geodetic conversion
);
print(pp);
// 006° 39' 14.832" N, 001° 32' 47.436" W, 200.000
print(res.asLatLng());
// 006° 39' 4.889" N, 001° 32' 48.303" W, 200.331
- Map Projections: Support for various map projections and functions to transform coordinates between different projections.
final LatLng pp = LatLng(6.65412, -1.54651, 200);
CoordinateConversion transCoordinate = CoordinateConversion();
CoordinateType sourceCoordinateType = CoordinateType.geodetic;
CoordinateType targetCoordinateType = CoordinateType.projected;
// Get WGS84 Geographic Coordinate System
Projection sourceProjection = Projection.get('EPSG:4326')!;
// Get UTM CRS
Projection targetProjectionUTM =
transCoordinate.getUTMProjection(pp.longitude);
var res = transCoordinate.convert(
point: pp,
projSrc: sourceProjection,
projDst: targetProjectionUTM,
conversion: transCoordinate.getConversionType(
sourceCoordinateType, targetCoordinateType),
//conversion: ConversionType.geodeticToProjected,
);
print(pp);
// 006° 39' 14.832" N, 001° 32' 47.436" W, 200.000
print(res);
// Eastings: 660671.6505858237
// Northings: 735749.4963174305
// Height: 200.0
Julian Dates
Julian Date Functions
The JulianDate
class in GeoEngine provides an interface to work with Julian Dates, a continuous count of days since the beginning of the Julian Period on January 1, 4713 BCE. This system is widely used in astronomy and other fields. Here's how you can utilize some of the main functions of this class:
Initialization
You can create a JulianDate
object in different ways:
From a specific date:
JulianDate date1 = JulianDate.fromDate(year: 2023, month: 8, day: 15);
Using a DateTime object:
var date = DateTime(2023, 8, 15);
JulianDate originalDate = JulianDate(date);
Comparing Julian Dates
You can compare two JulianDate
objects using the common comparison operators:
JulianDate date2 = JulianDate.fromDate(year: 2023, month: 8, day: 20);
print(date1 == date2); // false
print(date1 < date2); // true
print(date1 <= date2); // true
print(date1 > date2); // false
print(date1 >= date2); // false
Conversion Functions
To Julian Date:
double jd = originalDate.toJulianDate();
print('Julian Date: $jd');
// Julian Date: 2460171.5
To Modified Julian Date:
The Modified Julian Date (MJD) is calculated by subtracting 2,400,000.5 from the Julian Date. It's used for convenience and starts from November 17, 1858.
print('Modified Julian Date (1858/11/17): ${originalDate.toModifiedJulianDate()}');
// Modified Julian Date (1858/11/17): 60171.0
Referenced Julian Date:
You can also get a referenced Julian Date by specifying a reference date:
print('Referenced Julian Date (1960/01/01): ${originalDate.toModifiedJulianDate(referenceDate: DateTime(1960, 1, 1))}');
// Referenced Julian Date (1960/01/01): 23237.0
Converting Back to DateTime
If you have a Julian Date and wish to get the corresponding Gregorian date:
JulianDate convertedDate = JulianDate.fromJulianDate(jd);
print(convertedDate.dateTime);
// 2023-08-15 00:00:00.000
Example:
To get the Modified Julian Date with a specific reference date:
print(JulianDate(DateTime(2023, 1, 1)).toModifiedJulianDate(referenceDate: DateTime(1960, 1, 11)));
// 23001.0
Remember, always refer to the documentation or source code for any additional functions or nuances with the JulianDate
class in the GeoEngine library.
Least Squares Adjustment
Least Squares Adjustment
The LeastSquaresAdjustment
class in GeoEngine provides a robust way to perform least squares adjustments on geodetic and other types of data. This documentation breaks down the core components and usage of the class.
Overview
Least squares adjustment is a statistical method to solve an overdetermined system of equations. In the context of GeoEngine, this class can handle various scaling methods, and can be utilized for various geodetic computations including network adjustments.
Initialization
To initialize the LeastSquaresAdjustment
class, you need to provide the design matrix A
, the observation vector B
, and an optional weight matrix W
.
var lsa = LeastSquaresAdjustment(A: A, B: B);
Key Properties
Here are some of the core properties of the class:
x
: Unknown parameters.v
: Residuals.uv
: Unit variance.N
: The normal matrix.qxx
: Misclosure matrixcx
: Variance-Covariance of the Adjusted Heightscv
: Variance-Covariance of the Residualscl
: Variance-Covariance of the ObservationsstandardDeviation
: Standard deviation of the observations.standardError
: Standard error of the observations.standardErrorsOfUnknowns
: Standard errors of the unknowns.standardErrorsOfResiduals
: Standard errors of the residuals.standardErrorsOfObservations
: Standard errors of the observations.chiSquared
: Chi-squared value for the least squares adjustment.rejectionCriterion
: Rejection criterion for outlier detection, using the specified confidence level.outliers
: List of boolean values indicating whether each observation is an outlier (true) or not (false).
Methods
Chi-Square Test
To perform a Chi-Square goodness-of-fit test:
var chiSquareTest = lsa.chiSquareTest();
Covariance
To compute the covariance matrix:
var covMatrix = lsa.covariance();
Error Ellipse
Compute error ellipse parameters:
var eig = lsa.errorEllipse();
Outliers
Automatically remove outliers:
var newLsa = lsa.removeOutliersIteratively();
print(newLsa);
Confidence Intervals
Compute confidence intervals for the unknown parameters:
var lsa = LeastSquaresAdjustment(A: A, B: B);
var intervals = lsa.computeConfidenceIntervals();
print(intervals); // Output: [(lower1, upper1), (lower2, upper2), ...]
Custom Auto Scaling
Automatically scales or normalizes the matrices based on custom functions:
var scaledLsa = lsa.customAutoScale(
matrixNormalizationFunction: (Matrix A) => A.normalize(),
columnNormalizationFunction: (ColumnMatrix B) => B.normalize(),
diagonalNormalizationFunction: (DiagonalMatrix W) => W.normalize()
);
Examples
Here's an example to get you started:
var A = Matrix([
[-1, 0, 0, 0],
[-1, 1, 0, 0],
[0, -1, 1, 0],
[0, 0, -1, 0],
[0, 0, -1, 1],
[0, 0, 0, -1],
[1, 0, 0, -1],
]);
var W = DiagonalMatrix([1 / 16, 1 / 9, 1 / 49, 1 / 36, 1 / 16, 1 / 9, 1 / 25]);
var B = ColumnMatrix([0, 0, 0.13, 0, 0, -0.32, -0.53]);
var lsa = LeastSquaresAdjustment(A: A, B: B, W: W, confidenceLevel: 40);
var c = lsa.chiSquareTest();
print(c); // (chiSquared: 0.00340817748488164, degreesOfFreedom: 3)
print(lsa);
// Least Squares Adjustment Results:
// ---------------------------------
// Normal (N):
// Matrix: 4x4
// ┌ 0.2136111111111111 -0.1111111111111111 0.0 -0.04 ┐
// │ -0.1111111111111111 0.13151927437641722 -0.02040816326530612 0.0 │
// │ 0.0 -0.02040816326530612 0.1106859410430839 -0.0625 │
// └ -0.04 0.0 -0.0625 0.2136111111111111 ┘
//
// Unknown Parameters (x):
// Matrix: 4x1
// ┌ -0.06513489902716646 ┐
// │ -0.045703714070040764 │
// │ 0.1900882929187552 │
// └ 0.30911630747309227 ┘
//
// Residuals (v):
// Matrix: 7x1
// ┌ 0.06513489902716646 ┐
// │ 0.019431184957125695 │
// │ 0.10579200698879596 │
// │ -0.1900882929187552 │
// │ 0.11902801455433706 │
// │ 0.01088369252690774 │
// └ 0.1557487934997413 ┘
//
// Unit Variance (σ²): 0.0011360591616272134
//
// Standard Deviation (σ): 0.033705476730454556
//
// Chi-squared Test (Goodness-of-fit Test):
// Chi-squared value(χ²): 0.00340817748488164
// Degrees of Freedom: 3
//
// Standard Errors of Unknowns (Cx):
// [0.10509275934271714, 0.13339652325953671, 0.11787096037126248, 0.08536738678162376]
//
// Standard Errors of Residuals (Cv):
// [0.08445388398273435, 0.0340385190674456, 0.1853208260338704, 0.16433066214111094, 0.08042190803509813, 0.054193558000204894, 0.12767979681503475]
//
// Standard Errors of Observations (Cl):
// [0.10509275934271714, 0.09521508112867448, 0.14602428002855353, 0.11787096037126248, 0.10820934938363522, 0.08536738678162376, 0.1099970387144662]
//
// Rejection Criterion (Confidence Level 40.0): 0.01766173284889808
//
// Outliers (false = accepted, true = rejected):
// [false, true, false, false, true, false, false]
//
// Error Ellipse:
// [0.029441222484548304, 0.01187530402393495, 0.005032048784758579, 0.0036716992155236177]
//
// ---------------------------------
Levelling
Levelling
This file describes the Levelling
class, which represents a levelling survey. It allows you to define various parameters and perform calculations related to the survey. The class contains properties for the starting benchmark (TBM), closing TBM, accuracy, rounding digits, levelling method, etc. You can add measurements, compute reduced levels, get arithmetic checks, and print a summary of the results.
Initialization
To initialize the Levelling
class, you need to provide the accuracy accuracy
, method method
, starting TBM and an optional closing TBM.
var levelling = Levelling(
startingTBM: 100.0,
accuracy: 3,
roundDigits: 3,
method: LevellingMethod.riseFall,
);
Usage
You can start with or with the closing TBM.
// Initialize with starting TBM
final startingTBM = 100.000;
// Initialize with closing TBM
final closingTBM = 98.050;
// Create a new instance of Levelling with starting TBM, closing TBM, accuracy, method, rounding digits
final leveling = Levelling(
startingTBM: startingTBM,
closingTBM: closingTBM,
accuracy: 5,
method: LevellingMethod.riseFall,
roundDigits: 3,
);
The can be in a form of List<List<Object?>>
or list of LevellingMeasurement
objects.
// Create the sample observation data
final data = [
['A', 1.751, null, null],
['B', null, 0.540, null],
['C', 0.300, null, 2.100],
['D', null, 1.100, null],
['E', null, 1.260, null],
['F', 1.500, null, 2.300],
['G', null, null, 1.110]
];
// Add the data to the levelling object
for (int i = 0; i < data.length; i++) {
final row = data[i];
levelling.addMeasurement(LevellingMeasurement(
bs: row[1], is_: row[2], fs: row[3], station: row[0]));
}
// or use this
for (var entry in data) {
leveling.addData(entry[0].toString(), entry[1], entry[2], entry[3]);
}
You can get the result as a data frame or as a list of maps.
leveling.computeReducedLevels();
print("Rise & Fall:");
print(leveling.getDataFrame());
// Calculate reduced levels using Rise & Fall algorithm
leveling.computeReducedLevels(LevellingMethod.hpc);
print("\n\nHPC:");
print(leveling.getDataFrame());
Once the data are added to the Levelling
object, you can perform calculations. You can access all the results through the Levelling
object.
You can access all the properties of the object.
print(leveling.numberSTN); // 3
print(leveling.allowableMisclose); // 5.196
print(leveling.misclose); // -0.009
print(leveling.correction); // 0.009
print(leveling.adjustmentPerStation); // 0.003
print(leveling.reducedLevels); // [100.0, 101.211, 99.651, 98.851, 98.691, 97.651, 98.041]
print(leveling.isWorkAccepted); // Work is not accepted
print(leveling.arithmeticCheckResult);
// Arithmetic Checks:
// Sum of BS = 3.551
// Sum of FS = 5.510
// First RL = 100.000
// Last RL = 98.041
// Sum of BS - Sum of FS = -1.959
// Last RL - First RL = -1.959
// Arithmetic Checks are OK.
This can simply be printed by just calling the levelling
object for more detailed result.
print(leveling);
// ------ Levelling Summary -------
//
// Total measurements = 7
// Number of instrument stations = 3
// Starting TBM = 100.0
// Closing TBM = 98.05
//
// Allowable misclose = 8.660 mm
// Misclose = -0.009 m (-9.000 mm)
// Correction = 0.009
// Adjustment per station = 0.003
// Leveling Status: Work is not accepted.
//
// Arithmetic Checks:
// Sum of BS = 3.551
// Sum of FS = 5.510
// First RL = 100.000
// Last RL = 98.041
// Sum of BS - Sum of FS = -1.959
// Last RL - First RL = -1.959
// Arithmetic Checks are OK.
//
// BS IS FS Rise Fall Reduced Level (RL) Adjustment Adjusted RL Remarks
// ---------------------------------------------------------------------
// 1.751 100.000 0.000 100.000 A
// 0.540 1.211 101.211 0.003 101.214 B
// 0.300 2.100 -1.560 99.651 0.006 99.657 C
// 1.100 -0.800 98.851 0.006 98.857 D
// 1.260 -0.160 98.691 0.006 98.697 E
// 1.500 2.300 -1.040 97.651 0.009 97.660 F
// 1.110 0.390 98.041 0.009 98.050 G
Geocoding
Geocoding
Geocoding is the process of converting addresses or place names into geographic coordinates (latitude and longitude). This allows you to perform various spatial operations, such as finding distances between locations or visualizing data on a map. In this readme, I will introduce a Dart class library for geocoding that provides different strategies for using geocoding services.
initialize Geocoding
The GeoCoding library is designed to help you easily perform geocoding tasks in your Dart applications. It provides a set of classes and methods for working with geographic coordinates, addresses, and place names. The library supports multiple geocoding services and allows you to switch between them based on your needs.
Geocoder({
required Map<String, dynamic> strategyFactory,
Map<String, dynamic> config = const {},
Duration throttleDuration = const Duration(seconds: 1),
})
Strategies
The GeoCoder
library offers different strategies for using geocoding services:
GoogleStrategy
: This strategy uses the Google Maps Geocoding API to perform geocoding. You will need an API key from Google Cloud Platform to use this strategy.OpenStreetMapStrategy
: This strategy uses the OpenStreetMap Nominatim service for geocoding. It is a free and open-source service that does not require any API keys.LocalStrategy
: This strategy uses the local database of the device to perform geocoding. It is a fast and efficient way to perform geocoding.CustomStrategy
: This strategy allows you to provide your own geocoding service implementation. You can create a custom class that implements the required methods and use it as a strategy in theGeoCoding
library.
Usage GoogleStrategy
Google strategy is the default strategy that is used by the GeoCoder
library. It uses the Google Maps Geocoding API to perform geocoding. You will need an API key from Google Cloud Platform to use this strategy.
var point2 = LatLng(6, 0.7);
var googleGeocoder = Geocoder(
strategyFactory: GoogleStrategy.create('YOUR_GOOGLE_API_KEY'),
config: {
// Common Configurations
'language': 'en',
'requestTimeout': const Duration(seconds: 10),
// Google-Specific Configurations
'regionBias': 'US',
'resultType': 'address',
'locationType': 'ROOFTOP',
'components': 'country:US',
'rateLimit': 10, // Requests per second
}
);
GeocoderRequestResponse search = await googleGeocoder.search('Kotei');
print(search);
print('');
GeocoderRequestResponse rev = await googleGeocoder.reverse(point2);
print(rev);
print('');
Usage OpenStreetMapStrategy
var openStreetMapGeocoder =
Geocoder(strategyFactory: OpenStreetMapStrategy.create(), config: {
// Common Configurations
'language': 'en',
'requestTimeout': const Duration(seconds: 10),
// OpenStreetMap-Specific Configurations
'email': 'contact@example.com', // For Nominatim usage policy
'countryCodes': 'us,uk',
'viewBox': 'left,bottom,right,top',
'boundedViewBox': '1', //bounded to viewbox
'limit': 5,
'addressDetails': 1,
});
// Geocode an address
GeocoderRequestResponse search = await openStreetMapGeocoder.search('KNUST');
print(search);
print('');
// Reverse geocode coordinates to get the address
GeocoderRequestResponse rev = await openStreetMapGeocoder.reverse(point2);
print(rev);
print('');
Result:
Geocoding Search Query: knust, Success: true, Timestamp: 2024-02-19 04:56:54.024349
GeocoderRequestResponse:
Success: true
Duration: 1035ms
Result: [{place_id: 125221183, licence: Data © OpenStreetMap contributors, ODbL 1.0. http://osm.org/copyright, osm_type: way, osm_id: 32197062, lat: 53.741931199999996, lon: 9.842065240044334, class: natural, type: wood, place_rank: 22, importance: 0.2000099999999999, addresstype: wood, name: Knust, display_name: Knust, Quickborn, Kreis Pinneberg, Schleswig-Holstein, 25451, Germany, boundingbox: [53.7398546, 53.7444675, 9.8384852, 9.8500422]}, {place_id: 258247195, licence: Data © OpenStreetMap contributors, ODbL 1.0. http://osm.org/copyright, osm_type: way, osm_id: 378466289, lat: 6.6785135, lon: -1.5754220088808766, class: amenity, type: university, place_rank: 30, importance: 0.3752824455605189, addresstype: amenity, name: Kwame Nkrumah University of Science & Technology, display_name: Kwame Nkrumah University of Science & Technology, Osei Tutu II Boulevard, Ayigya, Kumasi, Oforikrom Municipal District, Ashanti Region, AK385, Ghana, boundingbox: [6.6617810, 6.6953608, -1.5894842, -1.5323729]}, {place_id: 108558563, licence: Data © OpenStreetMap contributors, ODbL 1.0. http://osm.org/copyright, osm_type: node, osm_id: 3362954799, lat: 49.5656112, lon: 9.4330658, class: place, type: locality, place_rank: 25, importance: 0.12500999999999995, addresstype: locality, name: Knust, display_name: Knust, Fuchsenloch, Waldstetten, Höpfingen, Verwaltungsverband Hardheim-Walldürn, Neckar-Odenwald-Kreis, Baden-Württemberg, 74746, Germany, boundingbox: [49.5556112, 49.5756112, 9.4230658, 9.4430658]}, {place_id: 122049626, licence: Data © OpenStreetMap contributors, ODbL 1.0. http://osm.org/copyright, osm_type: node, osm_id: 4874767389, lat: 51.3722208, lon: 8.7031219, class: highway, type: bus_stop, place_rank: 30, importance: 0.00000999999999995449, addresstype: highway, name: Knust, display_name: Knust, L 3393, Heringhausen, Diemelsee, Landkreis Waldeck-Frankenberg, Hesse, 34519, Germany, boundingbox: [51.3721708, 51.3722708, 8.7030719, 8.7031719]}, {place_id: 379344893, licence: Data © OpenStreetMap contributors, ODbL 1.0. http://osm.org/copyright, osm_type: node, osm_id: 1525291987, lat: 53.5581388, lon: 9.9679333, class: amenity, type: nightclub, place_rank: 30, importance: 0.00000999999999995449, addresstype: amenity, name: Knust, display_name: Knust, 30, Neuer Kamp, Karolinenviertel, St. Pauli, Hamburg-Mitte, Hamburg, 20357, Germany, boundingbox: [53.5580888, 53.5581888, 9.9678833, 9.9679833]}, {place_id: 98501560, licence: Data © OpenStreetMap contributors, ODbL 1.0. http://osm.org/copyright, osm_type: node, osm_id: 1979116123, lat: 51.3126212, lon: 7.9976981, class: highway, type: bus_stop, place_rank: 30, importance: 0.00000999999999995449, addresstype: highway, name: Knust, display_name: Knust, Silmecke, Seidfeld (Sauerland), Sundern, Hochsauerlandkreis, North Rhine-Westphalia, 59846, Germany, boundingbox: [51.3125712, 51.3126712, 7.9976481, 7.9977481]}]
Reverse Geocoding Query: Location(6.0, 0.7), Success: true, Timestamp: 2024-02-19 04:56:55.029766
GeocoderRequestResponse:
Success: true
Duration: 1003ms
Result: {place_id: 34177377, licence: Data © OpenStreetMap contributors, ODbL 1.0. http://osm.org/copyright, osm_type: way, osm_id: 517183740, lat: 5.999405087489885, lon: 0.7000142714680313, class: highway, type: unclassified, place_rank: 26, importance: 0.10000999999999993, addresstype: road, name: , display_name: Dabala, South Tongu District, Volta Region, Ghana, address: {town: Dabala, county: South Tongu District, state: Volta Region, ISO3166-2-lvl4: GH-TV, country: Ghana, country_code: gh}, boundingbox: [5.9949293, 5.9994108, 0.6890045, 0.7032672]}
Usage LocalStrategy
The LocalStrategy
in the GeoCoding
library allows you to use a pre-defined dataset for geocoding and reverse geocoding. This is useful when you want to work with an offline dataset or need to process large amounts of data quickly without relying on network requests to external services. The Local Strategy requires you to provide a set of coordinates along with their associated addresses, places, or locations.
To create a GeoCoder
instance using the Local Strategy, you can use the following code for dataset strategy:
List<Map<String, double>> points = [
{'latitude': 5.80736, 'longitude': 0.41074},
{'latitude': 6.13373, 'longitude': 0.81585},
{'latitude': 11.01667, 'longitude': -0.5},
{'latitude': 10.08587, 'longitude': -0.13587},
{'latitude': 9.35, 'longitude': -0.88333},
{'latitude': 10.73255, 'longitude': -1.05917},
];
Or using downloaded data from GeoNames data file with all world cities and population.
final geoData = await GeoData.readFile(
'example/GH.txt',
delimiter: '\t',
hasHeader: false,
coordinatesColumns: {
'latitude': 4,
'longitude': 5
}, // Specify column names and indices
);
// Print the number of records in the file
print(geoData.rows.length); // 23232
With the data created, geocoder can be used to create a local strategy using KDTree
indexing. Other indexing methods will be implemented soon.
The only difference is the connection to data and associating the coordinates to x
and y
axis.
In this example, the geoData
list contains the addresses, latitudes, and longitudes of different locations. The LocalStrategy.create()
function is used to create a geocoding strategy using the provided data and specify the column names for coordinates. You can customize various configuration options for the Local Strategy, including search radius, limit, data preprocessing logic, cache size, and indexing strategy.
Once you have created the localGeocoder
instance, you can use it to geocode an address or reverse geocode coordinates:
var localGeocoder = Geocoder(
strategyFactory: LocalStrategy.create(
entries: geoData.rows,
coordinatesColumnNames: (y: 'latitude', x: 'longitude'),
),
config: {
// Common Configurations
'language': 'en',
'requestTimeout': const Duration(seconds: 10),
// Local-Specific Configurations
'isGeodetic': true,
'searchRadius': 2000, // in meters
'limit': 5, // Number of results to return
'dataPreprocessing': (data) => {/* preprocessing logic */},
'cacheSize': 100,
'indexingStrategy': 'KDTree', // or 'RTree will be implemented soon'
});
// Geocode an address
GeocoderRequestResponse u = await localGeocoder.search('Kotei');
print(u);
print('');
// Reverse geocode coordinates to get the address
GeocoderRequestResponse rex = await localGeocoder.reverse(point2);
print(rex);
print('');
Geocoding Search Query: kotei, Success: true, Timestamp: 2024-02-19 05:13:55.706794
GeocoderRequestResponse:
Success: true
Duration: 42ms
Result: [{latitude: 6.66308, longitude: -1.55893, 0: 2299299, 1: Kotei, 2: Kotei, 3: Kotei, 4: 6.66308, 5: -1.55893, 6: P, 7: PPL, 8: GH, 9: , 10: 2, 11: 614, 12: , 13: , 14: 0, 15: , 16: 270, 17: Africa/Accra, 18: 06/12/2019}, {latitude: 6.60296, longitude: -1.66005, 0: 11780246, 1: Kotei, 2: Kotei, 3: "Kotei,Kotwi", 4: 6.60296, 5: -1.66005, 6: P, 7: PPL, 8: GH, 9: , 10: 2, 11: 613, 12: , 13: , 14: 0, 15: , 16: 242, 17: Africa/Accra, 18: 05/12/2019}]
Reverse Geocoding Query: Location(6.0, 0.7), Success: true, Timestamp: 2024-02-19 05:13:56.671350
GeocoderRequestResponse:
Success: true
Duration: 6ms
Result: [[{latitude: 5.98333, longitude: 0.7, 0: 2302105, 1: Dabala, 2: Dabala, 3: , 4: 5.98333, 5: 0.7, 6: H, 7: LK, 8: GH, 9: , 10: 0, 11: , 12: , 13: , 14: 0, 15: , 16: 1, 17: Africa/Accra, 18: 06/01/1994}, 1853.6194271649065], [{latitude: 5.98306, longitude: 0.69745, 0: 2305576, 1: Agbogbla, 2: Agbogbla, 3: "Agbogbla,Agoblan", 4: 5.98306, 5: 0.69745, 6: P, 7: PPL, 8: GH, 9: , 10: 8, 11: 401, 12: , 13: , 14: 0, 15: , 16: 3, 17: Africa/Accra, 18: 06/12/2019}, 1904.6339152449402]]
Initial Bearing: Bearing: 0.0° or 0.0 rad or 000° 00' 0.00000"
Final Bearing: Bearing: 180.0° or 3.1415926535897403 rad or 180° 00' 0.00000"
Distance (Haversine): 1.8536194271649065 km
Distance (Great Circle): 1.8536194278434843 km
Distance (Vincenty): 1.8434748739484934 km
Initial Bearing: Bearing: 8.514324846934187° or 0.14860300216336128 rad or 008° 30' 51.56945"
Final Bearing: Bearing: 188.51459101961586° or 3.2902003013427756 rad or 188° 30' 52.52767"
Distance (Haversine): 1.9046339152449403 km
Distance (Great Circle): 1.9046339162803452 km
The localGeocoder.search()
and localGeocoder.reverse()
functions work similarly to their online counterparts, allowing you to easily geocode addresses or reverse geocode coordinates from your pre-defined dataset.
Documentation
For detailed documentation and examples for each feature, please visit the GeoEngine Documentation.
Contributing
:beer: Pull requests are welcome!
Don't forget that open-source
makes no sense without contributors. No matter how big your changes are, it helps us a lot even it is a line of change.
There might be a lot of grammar issues in the docs. It's a big help to us to fix them if you are fluent in English. Reporting bugs and issues are contribution too, yes it is. Please file feature requests and bugs at the issue tracker.
Testing
Tests are located in the test directory. To run tests, execute dart test in the project root.
Author
Charles Gameti: gameticharles@GitHub.
License
GeoEngine is licensed under the Apache License - Version 2.0.