├── screenshots ├── pois_map.png ├── directions_map.png ├── isochrone_map.png ├── elevation_response.png ├── optimization_console.png └── reverse_geocoding_map.png ├── pubspec.yaml ├── test ├── services │ ├── isochrones_tests.dart │ ├── pois_tests.dart │ ├── optimization_tests.dart │ ├── matrix_tests.dart │ ├── elevation_tests.dart │ ├── directions_tests.dart │ └── geocode_tests.dart ├── open_route_service_test.dart └── miscellaneous │ └── geojson_tests.dart ├── lib ├── src │ ├── exceptions │ │ ├── matrix.dart │ │ ├── pois.dart │ │ └── base.dart │ ├── services │ │ ├── optimization.dart │ │ ├── pois.dart │ │ ├── isochrones.dart │ │ ├── matrix.dart │ │ ├── elevation.dart │ │ ├── directions.dart │ │ └── geocode.dart │ ├── models │ │ ├── pois_data_models.dart │ │ ├── ors_profile_enum.dart │ │ ├── coordinate_model.dart │ │ ├── elevation_data_models.dart │ │ ├── matrix_data_models.dart │ │ ├── direction_data_models.dart │ │ ├── geojson_feature_models.dart │ │ └── optimization_models │ │ │ ├── optimization_data_models.dart │ │ │ └── vroom_data_models.dart │ └── open_route_service_base.dart └── open_route_service.dart ├── .gitignore ├── LICENSE ├── analysis_options.yaml ├── example └── open_route_service_example.dart ├── .github └── workflows │ └── build_format_test.yml ├── CONTRIBUTING.md ├── CHANGELOG.md └── README.md /screenshots/pois_map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dhi13man/open_route_service/HEAD/screenshots/pois_map.png -------------------------------------------------------------------------------- /screenshots/directions_map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dhi13man/open_route_service/HEAD/screenshots/directions_map.png -------------------------------------------------------------------------------- /screenshots/isochrone_map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dhi13man/open_route_service/HEAD/screenshots/isochrone_map.png -------------------------------------------------------------------------------- /screenshots/elevation_response.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dhi13man/open_route_service/HEAD/screenshots/elevation_response.png -------------------------------------------------------------------------------- /screenshots/optimization_console.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dhi13man/open_route_service/HEAD/screenshots/optimization_console.png -------------------------------------------------------------------------------- /screenshots/reverse_geocoding_map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dhi13man/open_route_service/HEAD/screenshots/reverse_geocoding_map.png -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: open_route_service 2 | description: An encapsulation made around openrouteservice APIs, for Dart and Flutter projects, to easily generate Routes and their data. 3 | version: 1.2.7 4 | repository: https://github.com/dhi13man/open_route_service/ 5 | homepage: https://github.com/dhi13man/open_route_service/ 6 | issue_tracker: https://github.com/Dhi13man/open_route_service/issues 7 | 8 | environment: 9 | sdk: '>=2.14.1 <4.0.0' 10 | 11 | dependencies: 12 | http: ^1.0.0 13 | 14 | dev_dependencies: 15 | geodart: ^0.3.2 16 | lints: ^3.0.0 17 | test: ^1.20.1 18 | -------------------------------------------------------------------------------- /test/services/isochrones_tests.dart: -------------------------------------------------------------------------------- 1 | import 'package:open_route_service/open_route_service.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | void isochronesTests({ 5 | required OpenRouteService service, 6 | required List coordinates, 7 | }) { 8 | test( 9 | 'Fetch Isochrone Data using [isochronesGet]', 10 | () async { 11 | final GeoJsonFeatureCollection isochroneData = await service 12 | .isochronesPost(locations: coordinates, range: [300, 200]); 13 | expect(isochroneData.bbox.length, 2); 14 | expect(isochroneData.features.length, greaterThan(0)); 15 | expect(isochroneData.features.first.properties['group_index'], 0); 16 | }, 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /lib/src/exceptions/matrix.dart: -------------------------------------------------------------------------------- 1 | import 'package:open_route_service/src/exceptions/base.dart'; 2 | 3 | /// Exception thrown when Matrix data cannot be correctly parsed. 4 | class MatrixORSParsingException extends ORSParsingException { 5 | /// Creates a [MatrixORSParsingException] instance. 6 | /// 7 | /// - [uri]: the URI associated with the request (if any). 8 | /// - [cause]: the underlying exception (if any). 9 | /// - [causeStackTrace]: the accompanying stack trace (if any). 10 | const MatrixORSParsingException({ 11 | Uri? uri, 12 | Object? cause, 13 | StackTrace? causeStackTrace, 14 | }) : super( 15 | message: 16 | 'Matrix value cannot be determined from the given inputs as specified in https://openrouteservice.org/dev/#/api-docs/v2/matrix/{profile}/post.', 17 | uri: uri, 18 | cause: cause, 19 | causeStackTrace: causeStackTrace, 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /lib/src/exceptions/pois.dart: -------------------------------------------------------------------------------- 1 | import 'package:open_route_service/src/exceptions/base.dart'; 2 | 3 | /// Exception thrown when the POIs data is empty. 4 | class PoisEmptyORSParsingException extends ORSParsingException { 5 | /// Creates a [PoisEmptyORSParsingException] instance. 6 | /// 7 | /// - [uri]: the URI associated with the request (if any). 8 | /// - [cause]: the underlying exception (if any). 9 | /// - [causeStackTrace]: the accompanying stack trace (if any). 10 | const PoisEmptyORSParsingException({ 11 | Uri? uri, 12 | Object? cause, 13 | StackTrace? causeStackTrace, 14 | }) : super( 15 | message: 16 | 'Empty POIs data received. Verify that the input is correct and the endpoint is working as documented at https://openrouteservice.org/dev/#/api-docs/pois. If the endpoint is functional, your input may be invalid.', 17 | uri: uri, 18 | cause: cause, 19 | causeStackTrace: causeStackTrace, 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://www.dartlang.org/guides/libraries/private-files 2 | 3 | # Files and directories created by pub 4 | .dart_tool/ 5 | .packages 6 | build/ 7 | # If you're building an application, you may want to check-in your pubspec.lock 8 | pubspec.lock 9 | 10 | # Conventional directory for build outputs. 11 | build/ 12 | 13 | # Directory created by dartdoc 14 | # If you don't generate documentation locally you can remove this line. 15 | doc/api/ 16 | 17 | # dotenv environment variables file 18 | .env* 19 | dotenv* 20 | 21 | # Avoid committing generated Javascript files: 22 | *.dart.js 23 | *.info.json # Produced by the --dump-info flag. 24 | *.js # When generated by dart2js. Don't specify *.js if your 25 | # project includes source files written in JavaScript. 26 | *.js_ 27 | *.js.deps 28 | *.js.map 29 | 30 | .flutter-plugins 31 | .flutter-plugins-dependencies 32 | 33 | # IDE folders 34 | .vscode 35 | .idea 36 | 37 | # Mac-specific stuff 38 | .DS_Store -------------------------------------------------------------------------------- /lib/open_route_service.dart: -------------------------------------------------------------------------------- 1 | /// An encapsulation made around OpenRouteService API for Dart/Flutter projects. 2 | /// 3 | /// Made for easy generation of Directions, Isochrones, Time-Distance Matrix, 4 | /// Pelias Geocoding, etc. using their amazing API. 5 | library open_route_service; 6 | 7 | export 'package:open_route_service/src/models/coordinate_model.dart'; 8 | export 'package:open_route_service/src/models/direction_data_models.dart'; 9 | export 'package:open_route_service/src/models/elevation_data_models.dart'; 10 | export 'package:open_route_service/src/models/geojson_feature_models.dart'; 11 | export 'package:open_route_service/src/models/matrix_data_models.dart'; 12 | export 'package:open_route_service/src/models/optimization_models/optimization_data_models.dart'; 13 | export 'package:open_route_service/src/models/optimization_models/vroom_data_models.dart'; 14 | export 'package:open_route_service/src/models/pois_data_models.dart'; 15 | 16 | export 'package:open_route_service/src/open_route_service_base.dart'; 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Dhiman Seal 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:lints/recommended.yaml 2 | 3 | linter: 4 | rules: 5 | # ------ Disable individual rules ----- # 6 | # --- # 7 | # Turn off what you don't like. # 8 | # ------------------------------------- # 9 | 10 | # Prefer seeing Local Variable types on both sides of declaration 11 | omit_local_variable_types: false 12 | 13 | # Use parameter order as in Documentation Schema 14 | always_put_required_named_parameters_first: false 15 | 16 | # Util classes are awesome! 17 | avoid_classes_with_only_static_members: false 18 | 19 | # ------ Enable individual rules ------ # 20 | # --- # 21 | # These rules here are good but too # 22 | # opinionated to enable them by default # 23 | # ------------------------------------- # 24 | 25 | # Make constructors the first thing in every class 26 | sort_constructors_first: true 27 | 28 | # The new tabs vs. spaces. Choose wisely 29 | prefer_single_quotes: true 30 | 31 | # Blindly follow the Flutter code style, which prefers types everywhere 32 | always_specify_types: true 33 | -------------------------------------------------------------------------------- /test/services/pois_tests.dart: -------------------------------------------------------------------------------- 1 | import 'package:open_route_service/open_route_service.dart'; 2 | import 'package:open_route_service/src/exceptions/pois.dart'; 3 | import 'package:test/test.dart'; 4 | 5 | void poisTests({ 6 | required OpenRouteService service, 7 | required ORSCoordinate boundingBoxStart, 8 | required ORSCoordinate boundingBoxEnd, 9 | }) { 10 | test( 11 | 'Get POIs Data using geometry using [poisDataPostGet]', 12 | () async { 13 | try { 14 | final PoisData poisData = await service.poisDataPost( 15 | request: 'pois', 16 | geometry: { 17 | 'bbox': >[ 18 | boundingBoxStart.toList(), 19 | boundingBoxEnd.toList(), 20 | ], 21 | 'geojson': { 22 | 'type': 'Point', 23 | 'coordinates': boundingBoxStart.toList(), 24 | }, 25 | 'buffer': 200 26 | }, 27 | ); 28 | expect(poisData.features.length, greaterThan(0)); 29 | } on PoisEmptyORSParsingException catch (e) { 30 | if (e.uri?.path.contains('pois') ?? false) { 31 | print('POIs Endpoint Server failure! But package should be working!'); 32 | return; 33 | } else { 34 | rethrow; 35 | } 36 | } 37 | }, 38 | ); 39 | } 40 | -------------------------------------------------------------------------------- /test/services/optimization_tests.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:open_route_service/open_route_service.dart'; 4 | import 'package:test/test.dart'; 5 | 6 | void optimizationTests({ 7 | required OpenRouteService service, 8 | required String serializedJobs, 9 | required String serializedVehicles, 10 | }) { 11 | test( 12 | 'Fetch Optimizations from deserialized given Jobs and Vehicles using [optimizationDataPostGet]', 13 | () async { 14 | // Parse the Job and Vehicle data 15 | final List jobs = (json.decode(serializedJobs) as List) 16 | .map((dynamic e) => VroomJob.fromJson(e)) 17 | .toList(); 18 | expect(jobs.length, greaterThan(0)); // Validate deserialization 19 | 20 | final List vehicles = 21 | (json.decode(serializedVehicles) as List) 22 | .map((dynamic e) => VroomVehicle.fromJson(e)) 23 | .toList(); 24 | expect(vehicles.length, greaterThan(0)); // Validate deserialization 25 | 26 | // Test API using parsed data. 27 | final OptimizationData optimizationData = 28 | await service.optimizationDataPost(jobs: jobs, vehicles: vehicles); 29 | // Validate received data. 30 | expect(optimizationData.code, equals(0)); 31 | expect(optimizationData.routes.length, greaterThan(0)); 32 | }, 33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /example/open_route_service_example.dart: -------------------------------------------------------------------------------- 1 | import 'package:open_route_service/open_route_service.dart'; 2 | 3 | Future main() async { 4 | // Initialize the OpenRouteService with your API key. 5 | final OpenRouteService client = OpenRouteService(apiKey: 'YOUR-API-KEY'); 6 | 7 | // Example coordinates to test between 8 | const double startLat = 37.4220698; 9 | const double startLng = -122.0862784; 10 | const double endLat = 37.4111466; 11 | const double endLng = -122.0792365; 12 | 13 | // Form Route between coordinates 14 | final List routeCoordinates = 15 | await client.directionsRouteCoordsGet( 16 | startCoordinate: ORSCoordinate(latitude: startLat, longitude: startLng), 17 | endCoordinate: ORSCoordinate(latitude: endLat, longitude: endLng), 18 | ); 19 | 20 | // Print the route coordinates 21 | routeCoordinates.forEach(print); 22 | 23 | // Map route coordinates to a list of LatLng (requires google_maps_flutter) 24 | // to be used in Polyline 25 | // final List routePoints = routeCoordinates 26 | // .map((coordinate) => LatLng(coordinate.latitude, coordinate.longitude)) 27 | // .toList(); 28 | 29 | // Create Polyline (requires Material UI for Color) 30 | // final Polyline routePolyline = Polyline( 31 | // polylineId: PolylineId('route'), 32 | // visible: true, 33 | // points: routePoints, 34 | // color: Colors.red, 35 | // width: 4, 36 | // ); 37 | 38 | // Use Polyline to draw route on map or do anything else with the data :) 39 | } 40 | -------------------------------------------------------------------------------- /.github/workflows/build_format_test.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. 2 | # They are provided by a third-party and are governed by 3 | # separate terms of service, privacy policy, and support 4 | # documentation. 5 | 6 | name: Build, Format, Test 7 | 8 | on: 9 | push: 10 | branches: [ main ] 11 | pull_request: 12 | branches: [ main ] 13 | 14 | jobs: 15 | build_format_test: 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - uses: actions/checkout@v2 20 | 21 | # Note: This workflow uses the latest stable version of the Dart SDK. 22 | # You can specify other versions if desired, see documentation here: 23 | # https://github.com/dart-lang/setup-dart/blob/main/README.md 24 | # - uses: dart-lang/setup-dart@v1 25 | - uses: dart-lang/setup-dart@9a04e6d73cca37bd455e0608d7e5092f881fd603 26 | 27 | - name: Install dependencies 28 | run: dart pub get 29 | 30 | # Verify the use of 'dart format' on each commit. 31 | - name: Verify formatting 32 | run: dart format --output=none --set-exit-if-changed . 33 | 34 | # Consider passing '--fatal-infos' for slightly stricter analysis. 35 | - name: Analyze project source 36 | run: dart analyze 37 | 38 | # Ensure we didn't accidentally leak API key, and then run tests using Repository's Secret Environment API key. 39 | - name: Run tests 40 | run: dart test 41 | env: 42 | EXEC_ENV: 'github_actions' 43 | ORS_API_KEY: ${{ secrets.ORS_API_KEY }} 44 | -------------------------------------------------------------------------------- /lib/src/services/optimization.dart: -------------------------------------------------------------------------------- 1 | part of 'package:open_route_service/src/open_route_service_base.dart'; 2 | 3 | extension ORSOptimization on OpenRouteService { 4 | /// The endpoint of the openrouteservice Elevation API. 5 | String get _optimizationEndpointURL => '$_baseUrl/optimization'; 6 | 7 | /// Get the Optimization Data from openrouteservice for Vehicle routing 8 | /// problem scheduling, for the input [jobs], [vehicles] and optionally 9 | /// custom [matrix] and [options]. 10 | /// 11 | /// The optimization endpoint solves Vehicle Routing Problems and can be used 12 | /// to schedule multiple vehicles and jobs, respecting time windows, 13 | /// capacities and required skills. 14 | /// https://openrouteservice.org/dev/#/api-docs/optimization/post 15 | /// 16 | /// This service is based on the excellent Vroom project. Please also consult 17 | /// its API documentation. 18 | /// https://github.com/VROOM-Project/vroom/blob/master/docs/API.md 19 | Future optimizationDataPost({ 20 | required List jobs, 21 | required List vehicles, 22 | List? matrix, 23 | Object? options, 24 | }) async { 25 | // Build the request URL. 26 | final Uri uri = Uri.parse(_optimizationEndpointURL); 27 | 28 | // Ready data to be sent. 29 | final Map queryParameters = { 30 | 'jobs': jobs, 31 | 'vehicles': vehicles, 32 | 'matrix': matrix, 33 | 'options': options, 34 | }..removeWhere((String _, dynamic value) => value == null); 35 | 36 | // Fetch and parse the data. 37 | final Map data = 38 | await _openRouteServicePost(uri: uri, data: queryParameters); 39 | return OptimizationData.fromJson(data); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/src/services/pois.dart: -------------------------------------------------------------------------------- 1 | part of 'package:open_route_service/src/open_route_service_base.dart'; 2 | 3 | extension ORSPois on OpenRouteService { 4 | /// The endpoint of the openrouteservice Directions API. 5 | String get _poisEndpointURL => '$_baseUrl/pois'; 6 | 7 | /// Fetches the information about the Points of Interest (POI) in the area 8 | /// surrounding a geometry which can either be a bounding box, polygon 9 | /// or buffered linestring or point. 10 | /// 11 | /// [request] should be 'pois', 'stats' or 'list'. Throws an [ArgumentError] 12 | /// otherwise. 13 | /// 14 | /// The [geometry] object is a geojson or a bounding box object, 15 | /// optionally buffered. 16 | /// 17 | /// The [filters] are Filters in terms of osm_tags which should be applied 18 | /// to the query. 19 | /// 20 | /// Information about the endpoint, parameters, response etc. can be found at: 21 | /// https://openrouteservice.org/dev/#/api-docs/pois/post 22 | Future poisDataPost({ 23 | required String request, 24 | Object? geometry, 25 | Object? filters, 26 | int? limit, 27 | String? sortBy, 28 | }) async { 29 | // Validate [request] 30 | if (request != 'pois' && request != 'stats' && request != 'list') { 31 | throw ArgumentError.value( 32 | request, 33 | 'request', 34 | 'Must be one of "pois", "stats" or "list"', 35 | ); 36 | } 37 | 38 | // Build the request URL. 39 | final Uri uri = Uri.parse(_poisEndpointURL); 40 | 41 | // Prepare data to be sent. 42 | final Map queryParameters = { 43 | 'request': request, 44 | 'geometry': geometry, 45 | 'filters': filters, 46 | 'limit': limit, 47 | 'sortBy': sortBy, 48 | }..removeWhere((String _, dynamic value) => value == null); 49 | 50 | // Fetch the data and parse it. 51 | final dynamic data = 52 | await _openRouteServicePost(uri: uri, data: queryParameters); 53 | 54 | if (data is Map && data.isEmpty) { 55 | throw PoisEmptyORSParsingException(uri: uri); 56 | } else if (data is List) { 57 | return PoisData.fromJson(data.first); 58 | } else { 59 | return PoisData.fromJson(data); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /test/services/matrix_tests.dart: -------------------------------------------------------------------------------- 1 | import 'package:open_route_service/open_route_service.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | void matrixTests({ 5 | required OpenRouteService service, 6 | required List locations, 7 | }) { 8 | test( 9 | 'Get Matrix Data (without distances) using [matrixPostGet], for all profiles', 10 | () async { 11 | for (ORSProfile profile in ORSProfile.values) { 12 | final TimeDistanceMatrix matrix = await service.matrixPost( 13 | locations: locations, 14 | profileOverride: profile, 15 | ); 16 | 17 | final int numDestinations = matrix.destinations.length; 18 | final int numSources = matrix.sources.length; 19 | final int numDurations = matrix.durations.length; 20 | 21 | // Check if all the arrays except distances have been received 22 | expect(numDestinations, greaterThan(0)); 23 | expect(numSources, greaterThan(0)); 24 | expect(numDurations, greaterThan(0)); 25 | 26 | // Validate if the number of destinations, sources and durations match 27 | expect( 28 | numDestinations == numSources && numDestinations == numDurations, 29 | true, 30 | ); 31 | } 32 | }, 33 | ); 34 | 35 | test( 36 | 'Get Matrix Data (with distances) using [matrixPostGet]', 37 | () async { 38 | final TimeDistanceMatrix matrix = await service.matrixPost( 39 | locations: locations, 40 | metrics: ['distance', 'duration'], 41 | ); 42 | 43 | final int numDestinations = matrix.destinations.length; 44 | final int numSources = matrix.sources.length; 45 | final int numDurations = matrix.durations.length; 46 | final int numDistances = matrix.distances.length; 47 | 48 | // Check if all the arrays have been received 49 | expect(numDestinations, greaterThan(0)); 50 | expect(numSources, greaterThan(0)); 51 | expect(numDurations, greaterThan(0)); 52 | expect(numDistances, greaterThan(0)); 53 | 54 | // Validate if the number of destinations, sources and 55 | // durations, distances are equal 56 | expect(numSources == numDestinations, true); 57 | expect(numDurations == numDistances, true); 58 | }, 59 | ); 60 | } 61 | -------------------------------------------------------------------------------- /lib/src/models/pois_data_models.dart: -------------------------------------------------------------------------------- 1 | import 'package:open_route_service/open_route_service.dart'; 2 | 3 | /// Data class representing the Points Of Interest (POI)s of an area. 4 | /// 5 | /// Inherits a [GeoJsonFeatureCollection] and includes its bounding box 6 | /// coordinates [bbox] and [features]. Additionally includes [information] about 7 | /// the POIs. 8 | /// 9 | ///https://openrouteservice.org/dev/#/api-docs/pois 10 | class PoisData extends GeoJsonFeatureCollection { 11 | const PoisData({ 12 | required List bbox, 13 | required List features, 14 | required this.information, 15 | }) : super(bbox: bbox, features: features); 16 | 17 | factory PoisData.fromJson(Map json) => PoisData( 18 | bbox: [ 19 | ORSCoordinate(longitude: json['bbox'][0], latitude: json['bbox'][1]), 20 | ORSCoordinate(longitude: json['bbox'][2], latitude: json['bbox'][3]) 21 | ], 22 | features: (json['features'] as List) 23 | .map((dynamic e) => 24 | GeoJsonFeature.fromJson(e as Map)) 25 | .toList(), 26 | information: PoisInformation.fromJson(json['information']), 27 | ); 28 | 29 | final PoisInformation information; 30 | 31 | @override 32 | Map toJson() => super.toJson() 33 | ..addEntries( 34 | >[ 35 | MapEntry('information', information.toJson()), 36 | ], 37 | ); 38 | 39 | @override 40 | String toString() => toJson().toString(); 41 | } 42 | 43 | /// Stores the information associated with Pois. 44 | /// 45 | /// Includes the [attribution], [version] and [timestamp] of the Pois. 46 | /// 47 | /// Doesn't store query yet because it felt unnecessary(?). 48 | class PoisInformation { 49 | const PoisInformation({ 50 | required this.attribution, 51 | required this.version, 52 | required this.timestamp, 53 | }); 54 | 55 | factory PoisInformation.fromJson(Map json) => 56 | PoisInformation( 57 | attribution: json['attribution'] as String, 58 | version: json['version'] as String, 59 | timestamp: json['timestamp'], 60 | ); 61 | 62 | final String attribution; 63 | 64 | final String version; 65 | 66 | final int timestamp; 67 | 68 | Map toJson() => { 69 | 'attribution': attribution, 70 | 'version': version, 71 | 'timestamp': timestamp, 72 | }; 73 | 74 | @override 75 | String toString() => toJson().toString(); 76 | } 77 | -------------------------------------------------------------------------------- /lib/src/services/isochrones.dart: -------------------------------------------------------------------------------- 1 | part of 'package:open_route_service/src/open_route_service_base.dart'; 2 | 3 | extension ORServiceIsochrones on OpenRouteService { 4 | String get _isochronesEndpointURL => '$_baseUrl/v2/isochrones'; 5 | 6 | /// Obtain Isochrone (areas of reachability) Data for the [locations] given 7 | /// as a [List] of [ORSCoordinate]. 8 | /// 9 | /// The Isochrone Service supports time and distance analysis for one single 10 | /// or multiple locations. 11 | /// 12 | /// You may also specify the isochrone interval or provide multiple exact 13 | /// isochrone range values. 14 | /// 15 | /// The isochrone service supports the following [attributes]: 'area', 16 | /// 'reachfactor', 'total_pop'. 17 | /// 18 | /// Information about the endpoint, parameters, response etc. can be found at: 19 | /// https://openrouteservice.org/dev/#/api-docs/v2/isochrones/{profile}/post 20 | Future isochronesPost({ 21 | required List locations, 22 | required List range, 23 | List attributes = const [], 24 | String? id, 25 | bool intersections = false, 26 | int? interval, 27 | String locationType = 'start', 28 | Map? options, 29 | String rangeType = 'time', 30 | int? smoothing, 31 | String areaUnits = 'm', 32 | String units = 'm', 33 | ORSProfile? profileOverride, 34 | }) async { 35 | // If a path parameter override is provided, use it. 36 | final ORSProfile chosenPathParam = profileOverride ?? _defaultProfile; 37 | 38 | // Build the request URL. 39 | final Uri uri = 40 | Uri.parse('$_isochronesEndpointURL/${chosenPathParam.name}'); 41 | 42 | // Ready data to be sent. 43 | final Map queryParameters = { 44 | 'locations': locations 45 | .map>( 46 | (ORSCoordinate coordinate) => coordinate.toList(), 47 | ) 48 | .toList(), 49 | 'range': range, 50 | 'attributes': attributes, 51 | 'id': id, 52 | 'intersections': intersections, 53 | 'interval': interval, 54 | 'location_type': locationType, 55 | 'options': options, 56 | 'range_type': rangeType, 57 | 'smoothing': smoothing, 58 | 'area_units': areaUnits, 59 | 'units': units, 60 | }..removeWhere((String _, dynamic value) => value == null); 61 | 62 | // Fetch and parse the data. 63 | final Map data = 64 | await _openRouteServicePost(uri: uri, data: queryParameters); 65 | return GeoJsonFeatureCollection.fromJson(data); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /lib/src/services/matrix.dart: -------------------------------------------------------------------------------- 1 | part of 'package:open_route_service/src/open_route_service_base.dart'; 2 | 3 | extension ORSMatrix on OpenRouteService { 4 | /// The endpoint of the openrouteservice Matrix API. 5 | String get _matrixEndpointURL => '$_baseUrl/v2/matrix'; 6 | 7 | /// Returns duration, distance matrix for multiple source, destination points. 8 | /// 9 | /// [destinations] and [sources] are a list of [int] indices that represent 10 | /// which [locations] coordinates need to be considered for destinations and 11 | /// sources respectively. 12 | /// 13 | /// By default a square duration matrix is returned where every point in 14 | /// locations is paired with each other. 15 | /// 16 | /// Information about the endpoint, parameters, response etc. can be found at: 17 | /// https://openrouteservice.org/dev/#/api-docs/v2/matrix/{profile}/post 18 | Future matrixPost({ 19 | required List locations, 20 | List? destinations, 21 | String? id, 22 | List metrics = const ['duration'], 23 | List? metricsStrings, 24 | bool resolveLocations = false, 25 | List? sources, 26 | String units = 'm', 27 | ORSProfile? profileOverride, 28 | }) async { 29 | // If a path parameter override is provided, use it. 30 | final ORSProfile chosenPathParam = profileOverride ?? _defaultProfile; 31 | 32 | // Build the request URL. 33 | final Uri uri = Uri.parse('$_matrixEndpointURL/${chosenPathParam.name}'); 34 | 35 | // Prepare data to be sent. 36 | final Map queryParameters = { 37 | 'locations': locations 38 | .map>( 39 | (ORSCoordinate coordinate) => 40 | [coordinate.longitude, coordinate.latitude], 41 | ) 42 | .toList(), 43 | 'destinations': destinations, 44 | 'id': id, 45 | 'metrics': metrics, 46 | 'metricsStrings': metricsStrings, 47 | 'resolve_locations': resolveLocations, 48 | 'sources': sources, 49 | 'units': units, 50 | }..removeWhere((String _, dynamic value) => value == null); 51 | 52 | // Fetch the data. 53 | try { 54 | final Map data = 55 | await _openRouteServicePost(uri: uri, data: queryParameters); 56 | return TimeDistanceMatrix.fromJson(data); 57 | } on FormatException catch (e, trace) { 58 | throw MatrixORSParsingException( 59 | uri: uri, 60 | cause: e, 61 | causeStackTrace: trace, 62 | ); 63 | } on TypeError catch (e, trace) { 64 | throw MatrixORSParsingException( 65 | uri: uri, 66 | cause: e, 67 | causeStackTrace: trace, 68 | ); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /lib/src/models/ors_profile_enum.dart: -------------------------------------------------------------------------------- 1 | part of 'package:open_route_service/src/open_route_service_base.dart'; 2 | 3 | /// OpenRouteService API profiles as enum values to prevent typos in direct 4 | /// [String] usage. 5 | /// 6 | /// Corresponds to one of the profiles taken by openrouteservice: "driving-car", 7 | /// "driving-hgv", "cycling-road", "cycling-mountain", "cycling-electric", 8 | /// "foot-walking", "foot-hiking", "wheelchair". 9 | enum ORSProfile { 10 | drivingCar, 11 | drivingHgv, 12 | cyclingRoad, 13 | cyclingMountain, 14 | cyclingElectric, 15 | footWalking, 16 | footHiking, 17 | wheelchair, 18 | } 19 | 20 | /// Extension on [ORSProfile] to 21 | /// - Provide a [String] representation of the profile 22 | /// - Statically get [ORSProfile]s from [String]s. 23 | extension ORSProfileNamer on ORSProfile { 24 | static const Map _profileToNameMap = { 25 | ORSProfile.drivingCar: 'driving-car', 26 | ORSProfile.drivingHgv: 'driving-hgv', 27 | ORSProfile.cyclingRoad: 'cycling-road', 28 | ORSProfile.cyclingMountain: 'cycling-mountain', 29 | ORSProfile.cyclingElectric: 'cycling-electric', 30 | ORSProfile.footWalking: 'foot-walking', 31 | ORSProfile.footHiking: 'foot-hiking', 32 | ORSProfile.wheelchair: 'wheelchair', 33 | }; 34 | 35 | static const Map _nameToProfileMap = { 36 | 'driving-car': ORSProfile.drivingCar, 37 | 'driving-hgv': ORSProfile.drivingHgv, 38 | 'cycling-road': ORSProfile.cyclingRoad, 39 | 'cycling-mountain': ORSProfile.cyclingMountain, 40 | 'cycling-electric': ORSProfile.cyclingElectric, 41 | 'foot-walking': ORSProfile.footWalking, 42 | 'foot-hiking': ORSProfile.footHiking, 43 | 'wheelchair': ORSProfile.wheelchair, 44 | }; 45 | 46 | /// Returns the [String] representation of the openrouteservice profile 47 | /// represented by the enum. 48 | /// 49 | /// Throws [ArgumentError] if the enum is not a valid profile. 50 | String get name { 51 | if (_profileToNameMap.containsKey(this)) { 52 | return _profileToNameMap[this]!; 53 | } 54 | throw ArgumentError('Unknown ORSProfile: $this'); 55 | } 56 | 57 | /// Returns the [ORSProfile] represented by the [String] profileName. 58 | /// 59 | /// The [String] is case-sensitive and has to be one of: "driving-car", 60 | /// "driving-hgv", "cycling-road", "cycling-mountain", "cycling-electric", 61 | /// "foot-walking", "foot-hiking", "wheelchair". 62 | /// 63 | /// If the [String] is not one of the above, an [ArgumentError] is thrown. 64 | static ORSProfile fromName(final String profileName) { 65 | if (_nameToProfileMap.containsKey(profileName)) { 66 | return _nameToProfileMap[profileName]!; 67 | } 68 | throw ArgumentError('Unknown ORSProfile: $profileName'); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /lib/src/exceptions/base.dart: -------------------------------------------------------------------------------- 1 | /// The base exception class for the Open Route Service client, including an error [message], 2 | /// optional request [uri], underlying [cause], and corresponding [causeStackTrace]. 3 | abstract class ORSClientBaseException implements Exception { 4 | /// Creates an [ORSClientBaseException] instance. 5 | /// 6 | /// - [message]: the error message. 7 | /// - [uri]: the URI where the error occurred (if applicable). 8 | /// - [cause]: the underlying exception or error (if any). 9 | /// - [causeStackTrace]: the stack trace of the underlying exception (if any). 10 | const ORSClientBaseException( 11 | this.message, { 12 | this.uri, 13 | this.cause, 14 | this.causeStackTrace, 15 | }); 16 | 17 | /// The error message associated with the exception. 18 | final String message; 19 | 20 | /// The URI where the error occurred, if applicable. 21 | final Uri? uri; 22 | 23 | /// The underlying exception or error, if available. 24 | final Object? cause; 25 | 26 | /// The stack trace of the underlying exception, if available. 27 | final StackTrace? causeStackTrace; 28 | 29 | @override 30 | String toString() { 31 | final String causeStr = cause != null ? ', cause: $cause' : ''; 32 | final String stackStr = 33 | causeStackTrace != null ? ', stack: $causeStackTrace' : ''; 34 | return '$runtimeType: $message${uri != null ? ', at url $uri' : ''}$causeStr$stackStr'; 35 | } 36 | } 37 | 38 | /// Exception thrown during HTTP requests indicating request failures. 39 | class ORSHttpException extends ORSClientBaseException { 40 | /// Creates an [ORSHttpException] instance. 41 | /// 42 | /// - [uri]: the URI of the request. 43 | /// - [statusCode]: the HTTP status code. 44 | /// - [errorResponse]: the error response from the server (if any). 45 | /// - [cause]: the underlying exception (if any). 46 | /// - [causeStackTrace]: the accompanying stack trace (if any). 47 | const ORSHttpException({ 48 | required Uri uri, 49 | required this.statusCode, 50 | this.errorResponse, 51 | Object? cause, 52 | StackTrace? causeStackTrace, 53 | }) : super( 54 | 'Error during HTTP request', 55 | uri: uri, 56 | cause: cause, 57 | causeStackTrace: causeStackTrace, 58 | ); 59 | 60 | /// The HTTP status code returned from the request. 61 | final int statusCode; 62 | 63 | /// The error response returned by the server, if any. 64 | final Object? errorResponse; 65 | } 66 | 67 | /// Exception thrown when an error occurs during data parsing. 68 | class ORSParsingException extends ORSClientBaseException { 69 | /// Creates an [ORSParsingException] instance. 70 | /// 71 | /// - [message]: a custom error message. Defaults to 'Error while parsing data' if not provided. 72 | /// - [uri]: the URI associated with the parsing error (if any). 73 | /// - [cause]: the underlying exception (if any). 74 | /// - [causeStackTrace]: the accompanying stack trace (if any). 75 | const ORSParsingException({ 76 | String? message, 77 | Uri? uri, 78 | Object? cause, 79 | StackTrace? causeStackTrace, 80 | }) : super( 81 | message ?? 'Error while parsing data', 82 | uri: uri, 83 | cause: cause, 84 | causeStackTrace: causeStackTrace, 85 | ); 86 | } 87 | -------------------------------------------------------------------------------- /lib/src/models/coordinate_model.dart: -------------------------------------------------------------------------------- 1 | /// A Coordinate Data Model independent of any other external libraries. 2 | /// Contains a [double] Latitude, a [double] Longitude value and 3 | /// an optional [double] Altitude value. 4 | /// 5 | /// Should be easily convertible to a LatLng, GeoPoint etc for use in projects. 6 | class ORSCoordinate { 7 | /// Generates a coordinate from a [latitude] and [longitude]. 8 | const ORSCoordinate({ 9 | required this.latitude, 10 | required this.longitude, 11 | this.altitude, 12 | }); 13 | 14 | /// Generates a [ORSCoordinate] from a [Map] having [String] keys 15 | /// 'latitude' and 'longitude', respectively each having [double] values. 16 | factory ORSCoordinate.fromJson(Map json) => ORSCoordinate( 17 | latitude: json['latitude']! as double, 18 | longitude: json['longitude']! as double, 19 | altitude: json['altitude'] as double?, 20 | ); 21 | 22 | /// Generates a [ORSCoordinate] from a [List] having [double] values. 23 | factory ORSCoordinate.fromList(List json) => ORSCoordinate( 24 | longitude: (json[0]! as num).toDouble(), 25 | latitude: (json[1]! as num).toDouble(), 26 | altitude: json.length > 2 ? (json[2] as num?)?.toDouble() : null, 27 | ); 28 | 29 | /// The latitude of the coordinate. 30 | final double latitude; 31 | 32 | /// The longitude of the coordinate. 33 | final double longitude; 34 | 35 | /// The altitude of the coordinate. 36 | final double? altitude; 37 | 38 | /// Returns a [Map] having [String] keys 'latitude' and 'longitude', 39 | /// respectively having [latitude] and [longitude] as [double] values. 40 | Map toJson() => { 41 | 'latitude': latitude, 42 | 'longitude': longitude, 43 | 'altitude': altitude, 44 | }..removeWhere((String key, dynamic value) => value == null); 45 | 46 | /// Returns a [List] having [double] values. 47 | /// [longitude] and [latitude] are the first two values respectively. 48 | /// [altitude] is the third value if it exists (otherwise it is 0.0). 49 | List toList() => [ 50 | longitude, 51 | latitude, 52 | if (altitude != null) altitude!, 53 | ]; 54 | 55 | /// Adding two coordinates. 56 | ORSCoordinate operator +(ORSCoordinate other) => ORSCoordinate( 57 | latitude: latitude + other.latitude, 58 | longitude: longitude + other.longitude, 59 | altitude: (altitude == null && other.altitude == null) 60 | ? null 61 | : (altitude ?? 0.0) + (other.altitude ?? 0.0), 62 | ); 63 | 64 | /// Subtracting two coordinates. 65 | ORSCoordinate operator -(ORSCoordinate other) => ORSCoordinate( 66 | latitude: latitude - other.latitude, 67 | longitude: longitude - other.longitude, 68 | altitude: (altitude == null && other.altitude == null) 69 | ? null 70 | : (altitude ?? 0.0) - (other.altitude ?? 0.0), 71 | ); 72 | 73 | @override 74 | bool operator ==(Object other) => 75 | other is ORSCoordinate && 76 | other.latitude == latitude && 77 | other.longitude == longitude && 78 | other.altitude == altitude; 79 | 80 | @override 81 | int get hashCode => toJson().hashCode; 82 | 83 | @override 84 | String toString() => toJson().toString(); 85 | } 86 | -------------------------------------------------------------------------------- /test/services/elevation_tests.dart: -------------------------------------------------------------------------------- 1 | import 'package:open_route_service/open_route_service.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | double percentageError(double a, double b) { 5 | return (a - b).abs() / a * 100; 6 | } 7 | 8 | void elevationTests({ 9 | required OpenRouteService service, 10 | required ORSCoordinate coordinate, 11 | }) { 12 | test( 13 | 'Fetch Elevation using GET Method [elevationDataGet]', 14 | () async { 15 | // Attempt 1 using geojson format. 16 | const String formatOut1 = 'geojson'; 17 | final ElevationData elevationData = await service.elevationPointGet( 18 | geometry: coordinate, 19 | formatOut: formatOut1, 20 | ); 21 | 22 | // Validate that round-off error is less than 1%. 23 | expect( 24 | percentageError( 25 | elevationData.coordinates.first.longitude, 26 | coordinate.longitude, 27 | ), 28 | lessThan(1), 29 | ); 30 | expect( 31 | percentageError( 32 | elevationData.coordinates.first.latitude, 33 | coordinate.latitude, 34 | ), 35 | lessThan(1), 36 | ); 37 | 38 | // Attempt 2 using point format. 39 | const String formatOut2 = 'point'; 40 | final ElevationData elevationData2 = await service.elevationPointGet( 41 | geometry: coordinate, 42 | formatOut: formatOut2, 43 | ); 44 | 45 | // Validate that same data came out using both formats. 46 | expect(elevationData.coordinates, equals(elevationData2.coordinates)); 47 | }, 48 | ); 49 | 50 | test( 51 | 'Fetch Elevation using POST Method [elevationDataPostGet]', 52 | () async { 53 | // Attempt 1 using geojson format. 54 | const String formatOut1 = 'geojson'; 55 | final ElevationData elevationData = await service.elevationPointPost( 56 | geometry: coordinate, 57 | formatIn: formatOut1, 58 | formatOut: formatOut1, 59 | ); 60 | 61 | // Validate that round-off error is less than 1%. 62 | expect( 63 | percentageError( 64 | elevationData.coordinates.first.longitude, 65 | coordinate.longitude, 66 | ), 67 | lessThan(1), 68 | ); 69 | expect( 70 | percentageError( 71 | elevationData.coordinates.first.latitude, 72 | coordinate.latitude, 73 | ), 74 | lessThan(1), 75 | ); 76 | 77 | // Attempt 2 using point format. 78 | const String formatOut2 = 'point'; 79 | final ElevationData elevationData2 = await service.elevationPointPost( 80 | geometry: coordinate, 81 | formatIn: formatOut2, 82 | formatOut: formatOut2, 83 | ); 84 | 85 | // Validate that same data came out using both formats. 86 | expect(elevationData.coordinates, equals(elevationData2.coordinates)); 87 | }, 88 | ); 89 | 90 | test( 91 | 'Cross-validate GET and POST Elevation fetching methods [elevationDataGet] and [elevationDataPostGet]', 92 | () async { 93 | // Attempt 1 using GET 94 | final ElevationData elevationDataGet = 95 | await service.elevationPointGet(geometry: coordinate); 96 | 97 | // Attempt 2 using POST 98 | final ElevationData elevationDataPost = await service.elevationPointPost( 99 | geometry: coordinate, 100 | formatIn: 'point', 101 | ); 102 | 103 | // Validate both methods returned same inherent result. 104 | expect(elevationDataGet, equals(elevationDataPost)); 105 | }, 106 | ); 107 | 108 | test( 109 | 'Fetch Elevation through planar 2D Line Geometry using [elevationDataLinePostGet]', 110 | () async { 111 | final ElevationData elevationData = await service.elevationLinePost( 112 | geometry: 'u`rgFswjpAKD', 113 | formatIn: 'encodedpolyline5', 114 | ); 115 | expect(elevationData.coordinates.length, greaterThan(1)); 116 | }, 117 | ); 118 | } 119 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # open_route_service 2 | 3 | [![License](https://img.shields.io/github/license/dhi13man/open_route_service)](https://github.com/Dhi13man/open_route_service/blob/main/LICENSE) 4 | [![Contributors](https://img.shields.io/github/contributors-anon/dhi13man/open_route_service?style=flat)](https://github.com/Dhi13man/open_route_service/graphs/contributors) 5 | [![GitHub forks](https://img.shields.io/github/forks/dhi13man/open_route_service?style=social)](https://github.com/Dhi13man/open_route_service/network/members) 6 | [![GitHub Repo stars](https://img.shields.io/github/stars/dhi13man/open_route_service?style=social)](https://github.com/Dhi13man/open_route_service) 7 | [![Last Commit](https://img.shields.io/github/last-commit/dhi13man/open_route_service)](https://github.com/Dhi13man/open_route_service/commits/main) 8 | 9 | Thank you for investing your time in contributing to this project! Any contributions you make have a chance of reflecting in [the actual Package on Pub.dev](https://pub.dev/packages/open_route_service/), and brightens up my day. :) 10 | 11 | Make sure you go through the [API Documentation](https://openrouteservice.org/dev/#/api-docs/) first! 12 | 13 | ## General Steps to Contribute 14 | 15 | 1. Ensure you have [Dart](https://dart.dev/get-dart)/[Flutter](https://flutter.dev/docs/get-started/install/) SDK installed. 16 | 17 | 2. Fork the [project repository](https://github.com/Dhi13man/open_route_service/). 18 | 19 | 3. Clone the forked repository by running `git clone `. 20 | 21 | 4. Navigate to your local repository by running `cd open_route_service`. 22 | 23 | 5. Pull the latest changes from upstream into your local repository by running `git pull`. 24 | 25 | 6. Create a new branch by running `git checkout -b `. 26 | 27 | 7. Make changes in your local repository to make the contribution you want. 28 | 1. Data Model files go to `./lib/src/models/`. 29 | 2. API files go to `./lib/src/services/`. 30 | 31 | 8. Add relevant tests (if any) for the contribution you made to `./test/` folder and an appropriate subfolder. 32 | 33 | 9. **Get an [openrouteservice API Key](https://openrouteservice.org/dev/#/signup/)** if you haven't already, and set it as the `apiKey` constant in `./test/open_route_service_test.dart` in place of `'test'`. 34 | 35 | 10. Run `dart test` to run the tests. **Ensure all tests run and pass before committing and/or pushing!** 36 | 37 | 11. **Replace your `apiKey` with `'test'` again before committing and/or pushing**, or it will get leaked! 38 | 39 | 12. Commit your changes and push them to your local repository by running `git commit -am "my-commit-message" && git push origin `. 40 | 41 | 13. Create a pull request on the original repository from your fork and wait for me to review (and hopefully merge) it. :) 42 | 43 | ### Recommended Development Workflow 44 | 45 | - Fork Project **->** Create new Branch 46 | - For each contribution in mind, 47 | - **->** Develop Data Models 48 | - **->** Develop API Bindings 49 | - **->** Test 50 | - **->** Ensure Documentation is sufficient 51 | - **->** Commit 52 | - Create Pull Request 53 | 54 | ## Issue Based Contributions 55 | 56 | ### Create a new issue 57 | 58 | If you spot a problem or bug with the package, search if an [issue](https://www.github.com/dhi13man/open_route_service/issues/) already exists. If a related issue doesn't exist, you can open a new issue using a relevant issue form. 59 | 60 | ### Solve an issue 61 | 62 | Scan through our existing [issues](https://www.github.com/dhi13man/open_route_service/issues/) to find one that interests you. You can narrow down the search using labels as filters. See Labels for more information. 63 | 64 | ## Overall Guidelines 65 | 66 | - Contributions are welcome on [GitHub](https://www.github.com/dhi13man/open_route_service/). Please ensure all the tests are running before pushing your changes. Write your own tests too! 67 | 68 | - File any [issues or feature requests here,](https://www.github.com/dhi13man/open_route_service/issues/) or help me resolve existing ones. :) 69 | -------------------------------------------------------------------------------- /lib/src/services/elevation.dart: -------------------------------------------------------------------------------- 1 | part of 'package:open_route_service/src/open_route_service_base.dart'; 2 | 3 | extension ORSElevation on OpenRouteService { 4 | /// The endpoint of the openrouteservice Elevation API. 5 | String get _elevationEndpointURL => '$_baseUrl/elevation'; 6 | 7 | /// Fetches the [ElevationData] by taking a 2D [geometry] and enriching it 8 | /// with elevation from a variety of datasets. Uses the GET method for the 9 | /// endpoint. 10 | /// 11 | /// Information about the endpoint, parameters, response etc. can be found at: 12 | /// https://openrouteservice.org/dev/#/api-docs/elevation/point/get 13 | Future elevationPointGet({ 14 | required ORSCoordinate geometry, 15 | String formatOut = 'geojson', 16 | String dataset = 'srtm', 17 | }) async { 18 | // Extract coordinate information. 19 | final double lat = geometry.latitude; 20 | final double lng = geometry.longitude; 21 | 22 | // Build the request URL. 23 | final Uri uri = Uri.parse( 24 | '$_elevationEndpointURL/point?api_key=$_apiKey&geometry=$lng,$lat&format_out=$formatOut&dataset=$dataset', 25 | ); 26 | final Map data = await _openRouteServiceGet(uri: uri); 27 | return ElevationData.fromJson(data); 28 | } 29 | 30 | /// Fetches the [ElevationData] by taking a 2D [coordinate] and enriching it 31 | /// with elevation from a variety of datasets. Uses the POST method for the 32 | /// endpoint. 33 | /// 34 | /// Information about the endpoint, parameters, response etc. can be found at: 35 | /// https://openrouteservice.org/dev/#/api-docs/elevation/point/post 36 | Future elevationPointPost({ 37 | required ORSCoordinate geometry, 38 | String formatIn = 'point', 39 | String formatOut = 'geojson', 40 | String dataset = 'srtm', 41 | }) async { 42 | // Build the request URL. 43 | final Uri uri = Uri.parse('$_elevationEndpointURL/point'); 44 | 45 | // Ready data to be sent. 46 | final Map queryParameters = { 47 | 'format_in': formatIn, 48 | 'format_out': formatOut, 49 | 'dataset': dataset, 50 | 'geometry': formatIn == 'geojson' 51 | ? { 52 | 'type': 'Point', 53 | 'coordinates': geometry.toList(), 54 | } 55 | : geometry.toList(), 56 | }; 57 | 58 | // Fetch and parse the data. 59 | final Map data = 60 | await _openRouteServicePost(uri: uri, data: queryParameters); 61 | return ElevationData.fromJson(data); 62 | } 63 | 64 | /// Fetches the [ElevationData] by taking planar 2D line objects [geometry] 65 | /// and enriching them with elevation from a variety of datasets. 66 | /// 67 | /// Information about the endpoint, parameters, response etc. can be found at: 68 | /// https://openrouteservice.org/dev/#/api-docs/elevation/line/post 69 | Future elevationLinePost({ 70 | required Object geometry, 71 | required String formatIn, 72 | String formatOut = 'geojson', 73 | String dataset = 'srtm', 74 | }) async { 75 | // Validate if geometry is correct formats. Check documentation for details. 76 | if (geometry is! String && 77 | geometry is! List> && 78 | geometry is! Map) { 79 | throw ArgumentError.value( 80 | geometry, 81 | 'geometry', 82 | 'Must be a String, List> or Map.', 83 | ); 84 | } 85 | // Build the request URL. 86 | final Uri uri = Uri.parse('$_elevationEndpointURL/line'); 87 | 88 | // Ready data to be sent. 89 | final Map queryParameters = { 90 | 'format_in': formatIn, 91 | 'format_out': formatOut, 92 | 'dataset': dataset, 93 | 'geometry': geometry, 94 | }; 95 | 96 | // Fetch and parse the data. 97 | final Map data = 98 | await _openRouteServicePost(uri: uri, data: queryParameters); 99 | return ElevationData.fromJson(data); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /test/open_route_service_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:test/test.dart'; 4 | 5 | import 'miscellaneous/geojson_tests.dart'; 6 | import 'services/directions_tests.dart'; 7 | import 'services/elevation_tests.dart'; 8 | import 'services/geocode_tests.dart'; 9 | import 'services/isochrones_tests.dart'; 10 | import 'services/matrix_tests.dart'; 11 | import 'services/optimization_tests.dart'; 12 | import 'services/pois_tests.dart'; 13 | 14 | import 'package:open_route_service/open_route_service.dart'; 15 | 16 | Future main() async { 17 | // TODO: Change the API key to your own API key to ensure that package works. 18 | String apiKey = 'test'; 19 | 20 | // Change API key from environment if tests are running on Github Actions. 21 | if ((Platform.environment['EXEC_ENV'] ?? '') == 'github_actions') { 22 | // If running on Github Actions, the last pusher shouldn't have leaked 23 | // their API key. 24 | assert(apiKey == 'test'); 25 | apiKey = Platform.environment['ORS_API_KEY']!; 26 | } 27 | // Dummy Coordinates 28 | const ORSCoordinate dirStartCoordinate = 29 | ORSCoordinate(latitude: 37.4220698, longitude: -122.0862784); 30 | const ORSCoordinate dirEndCoordinate = 31 | ORSCoordinate(latitude: 37.4111466, longitude: -122.0792365); 32 | const ORSCoordinate isochroneStartCoordinate = 33 | ORSCoordinate(latitude: 49.41461, longitude: 8.681495); 34 | const ORSCoordinate isochroneEndCoordinate = 35 | ORSCoordinate(latitude: 49.41943, longitude: 8.686507); 36 | 37 | // Dummy Optimization Jobs and Vehicles 38 | const String serializedJobs = 39 | '[{"id":1,"service":300,"amount":[1],"location":[1.98935,48.701],"skills":[1],"time_windows":[[32400,36000]]},{"id":2,"service":300,"amount":[1],"location":[2.03655,48.61128],"skills":[1]},{"id":3,"service":300,"amount":[1],"location":[2.39719,49.07611],"skills":[2]},{"id":4,"service":300,"amount":[1],"location":[2.41808,49.22619],"skills":[2]},{"id":5,"service":300,"amount":[1],"location":[2.28325,48.5958],"skills":[14]},{"id":6,"service":300,"amount":[1],"location":[2.89357,48.90736],"skills":[14]}]'; 40 | const String serializedVehicles = 41 | ' [{"id":1,"profile":"driving-car","start":[2.35044,48.71764],"end":[2.35044,48.71764],"capacity":[4],"skills":[1,14],"time_window":[28800,43200]},{"id":2,"profile":"driving-car","start":[2.35044,48.71764],"end":[2.35044,48.71764],"capacity":[4],"skills":[2,14],"time_window":[28800,43200]}]'; 42 | 43 | final OpenRouteService service = OpenRouteService(apiKey: apiKey); 44 | 45 | // Begin tests! 46 | group('Initial test', () { 47 | test('Verify that API Key was Set before testing!', () async { 48 | expect(apiKey != 'test', true); 49 | }); 50 | }); 51 | 52 | group( 53 | 'Directions API tests:', 54 | () => directionsTests( 55 | service: service, 56 | startCoordinate: dirStartCoordinate, 57 | endCoordinate: dirEndCoordinate, 58 | ), 59 | ); 60 | 61 | group( 62 | 'Elevation API tests:', 63 | () => elevationTests(service: service, coordinate: dirStartCoordinate), 64 | ); 65 | 66 | group( 67 | 'Isochrones API tests:', 68 | () => isochronesTests( 69 | service: service, 70 | coordinates: [ 71 | isochroneStartCoordinate, 72 | isochroneEndCoordinate, 73 | ], 74 | ), 75 | ); 76 | 77 | group( 78 | 'Matrix API tests:', 79 | () => matrixTests( 80 | service: service, 81 | locations: [dirStartCoordinate, dirEndCoordinate], 82 | ), 83 | ); 84 | 85 | group( 86 | 'POIs API tests:', 87 | () => poisTests( 88 | service: service, 89 | boundingBoxStart: isochroneStartCoordinate, 90 | boundingBoxEnd: isochroneEndCoordinate, 91 | ), 92 | ); 93 | 94 | group( 95 | 'Optimization API tests:', 96 | () => optimizationTests( 97 | service: service, 98 | serializedJobs: serializedJobs, 99 | serializedVehicles: serializedVehicles, 100 | ), 101 | ); 102 | 103 | group( 104 | 'Geocoding API tests:', 105 | () => geocodeTests( 106 | service: service, 107 | geocodeQueryText: 'Namibian Brewery', 108 | geocodeLocalityQueryText: 'Paris', 109 | geocodeReversePoint: 110 | const ORSCoordinate(longitude: 2.294471, latitude: 48.858268), 111 | reverseGeocodeQueryLocality: 'Paris', 112 | ), 113 | ); 114 | 115 | group('GeoJSON Compatibility Tests', () => geoJsonTests()); 116 | } 117 | -------------------------------------------------------------------------------- /lib/src/models/elevation_data_models.dart: -------------------------------------------------------------------------------- 1 | import 'package:open_route_service/src/models/coordinate_model.dart'; 2 | 3 | /// Class representing 2D point enriched it with elevation from a 4 | /// variety of datasets. 5 | /// 6 | /// Has bad [ElevationData.fromJson] code because of inconsistency of Elevation 7 | /// data in OpenRouteService API. 8 | /// 9 | /// Includes its [coordinates], [timestamp], [attribution], [type] and [version] 10 | /// of the data. 11 | /// 12 | /// https://openrouteservice.org/dev/#/api-docs/elevation/point/get 13 | class ElevationData { 14 | const ElevationData({ 15 | required this.coordinates, 16 | required this.timestamp, 17 | this.attribution = 18 | 'service by https://openrouteservice.org | data by https://srtm.csi.cgiar.org', 19 | this.type = 'point', 20 | this.version = '0.2.1', 21 | }); 22 | 23 | /// Construct Elevation data from JSON as per the schema in the api 24 | /// documentation. 25 | /// 26 | /// Bad code because of inconsistency of Elevation data in OpenRouteService. 27 | /// 28 | /// The json should have keys 'timestamp', 'attribution', 'version', 29 | /// 'elevation' and 'geometry' which corresponds to a [Map] further 30 | /// containing keys 'coordinates' and 'type'. 31 | factory ElevationData.fromJson(Map json) => ElevationData( 32 | coordinates: json['geometry'] is Map && 33 | json['geometry']['coordinates'] is List && 34 | (json['geometry']['coordinates'] as List).first 35 | is List 36 | ? (json['geometry']['coordinates'] as List) 37 | .map( 38 | (dynamic coordinate) => ORSCoordinate.fromList(coordinate), 39 | ) 40 | .toList() 41 | : [ 42 | ORSCoordinate( 43 | latitude: json['geometry'] is List 44 | ? (json['geometry']?[1] as double) 45 | : (json['geometry']?['coordinates']?[1]! as double), 46 | longitude: json['geometry'] is List 47 | ? (json['geometry']?[0] as double) 48 | : (json['geometry']?['coordinates']?[0]! as double), 49 | altitude: json['geometry'] is List 50 | ? (json['geometry']?[2] as double) 51 | : (json['geometry']?['coordinates']?[2]! as num) 52 | .toDouble(), 53 | ), 54 | ], 55 | timestamp: json['timestamp']! as int, 56 | attribution: json['attribution'] as String, 57 | type: (json['geometry'] is List) 58 | ? 'point' 59 | : (json['geometry']?['type'] ?? 'point') as String, 60 | version: json['version'] as String, 61 | ); 62 | 63 | /// Attribution to the source the elevation data has been extracted from. 64 | final String attribution; 65 | 66 | /// The coordinates of the elevation Geometry data. [Lat, Lng, Alt?] 67 | final List coordinates; 68 | 69 | /// The type of the elevation Geometry data. 70 | final String type; 71 | 72 | /// Timestamp when the elevation data was extracted. 73 | final int timestamp; 74 | 75 | /// Version of the elevation data. 76 | final String version; 77 | 78 | /// Construct JSON from the elevation data, as per the schema in the api 79 | /// documentation. 80 | /// 81 | /// The json will have keys 'timestamp', 'attribution', 'version', 'elevation' 82 | /// and 'geometry' which corresponds to a [Map] further containing keys 83 | /// 'coordinates' and 'type'. 84 | Map toJson() => { 85 | 'attribution': attribution, 86 | 'geometry': { 87 | 'coordinates': 88 | coordinates.map>((dynamic e) => e.toList()).toList(), 89 | 'type': type, 90 | }, 91 | 'timestamp': timestamp, 92 | 'version': version, 93 | }; 94 | 95 | @override 96 | bool operator ==(Object other) => 97 | other is ElevationData && 98 | other.attribution == attribution && 99 | other.coordinates.toString() == coordinates.toString() && 100 | other.type == type && 101 | other.version == version; 102 | 103 | @override 104 | int get hashCode => toJson().hashCode; 105 | 106 | @override 107 | String toString() => toJson().toString(); 108 | } 109 | -------------------------------------------------------------------------------- /test/services/directions_tests.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'package:open_route_service/open_route_service.dart'; 4 | import 'package:test/test.dart'; 5 | 6 | void directionsTests({ 7 | required OpenRouteService service, 8 | required ORSCoordinate startCoordinate, 9 | required ORSCoordinate endCoordinate, 10 | }) { 11 | test( 12 | 'Fetch and parse route for 2 points using [directionsRouteCoordsGet], for all profiles', 13 | () async { 14 | // Validate API for each profile 15 | for (ORSProfile profile in ORSProfile.values) { 16 | final List routeCoordinates = 17 | await service.directionsRouteCoordsGet( 18 | startCoordinate: startCoordinate, 19 | endCoordinate: endCoordinate, 20 | profileOverride: profile, 21 | ); 22 | expect(routeCoordinates.length, greaterThan(1)); 23 | } 24 | }, 25 | ); 26 | 27 | test( 28 | 'Error Validation for 2 point route in first and last path points [directionsRouteCoordsGet]', 29 | () async { 30 | final List routeCoordinates = 31 | await service.directionsRouteCoordsGet( 32 | startCoordinate: startCoordinate, 33 | endCoordinate: endCoordinate, 34 | ); 35 | final ORSCoordinate first = routeCoordinates.first, 36 | last = routeCoordinates.last; 37 | 38 | // Calculate percentage error in first and last path points 39 | final double startLatErr = 40 | (first.latitude - startCoordinate.latitude).abs() / 41 | startCoordinate.latitude * 42 | 100; 43 | final double startLngErr = 44 | (first.longitude - startCoordinate.longitude).abs() / 45 | startCoordinate.longitude * 46 | 100; 47 | final double endLatErr = (last.latitude - endCoordinate.latitude).abs() / 48 | endCoordinate.latitude * 49 | 100; 50 | final double endLngErr = 51 | (last.longitude - endCoordinate.longitude).abs() / 52 | endCoordinate.longitude * 53 | 100; 54 | 55 | // Validate that the first and last points of the route are not too far 56 | // from the start and end points (less than 0.1% error) 57 | expect((startLatErr + startLngErr).abs() / 2.0, lessThan(0.1)); 58 | expect((endLatErr + endLngErr).abs() / 2.0, lessThan(0.1)); 59 | }, 60 | ); 61 | 62 | test( 63 | 'Fetch and parse route for multiple points using [directionsMultiRouteCoordsPostGet]', 64 | () async { 65 | final List routeCoordinates = 66 | await service.directionsMultiRouteCoordsPost( 67 | coordinates: [ 68 | startCoordinate, 69 | endCoordinate, 70 | startCoordinate, 71 | ], 72 | ); 73 | expect(routeCoordinates.length, greaterThan(0)); 74 | }); 75 | 76 | test( 77 | 'Cross-validate [directionsRouteCoordsGet] and [directionsMultiRouteCoordsPostGet]', 78 | () async { 79 | final List routeCoordinates = 80 | await service.directionsRouteCoordsGet( 81 | startCoordinate: startCoordinate, 82 | endCoordinate: endCoordinate, 83 | ); 84 | final List routeCoordinatesMulti = 85 | await service.directionsMultiRouteCoordsPost( 86 | coordinates: [startCoordinate, endCoordinate], 87 | ); 88 | 89 | // Validate that the route coordinates are the same as in each case route 90 | // will be same despite using different APIs. 91 | final int minLength = 92 | min(routeCoordinates.length, routeCoordinatesMulti.length); 93 | for (int i = 0; i < minLength; i++) { 94 | expect(routeCoordinates[i], routeCoordinatesMulti[i]); 95 | } 96 | }); 97 | 98 | test('Get Directions API Route Data using [directionsMultiRouteDataPostGet]', 99 | () async { 100 | final List directionRouteData = 101 | await service.directionsMultiRouteDataPost( 102 | coordinates: [startCoordinate, endCoordinate], 103 | ); 104 | expect(directionRouteData.length, greaterThan(0)); 105 | final DirectionRouteData firstRoute = directionRouteData.first; 106 | expect(firstRoute.bbox.length, equals(2)); 107 | 108 | double totalDistance = 0; 109 | for (DirectionRouteSegment segment in firstRoute.segments) { 110 | totalDistance += segment.distance; 111 | } 112 | expect(firstRoute.summary.distance, totalDistance); 113 | }); 114 | } 115 | -------------------------------------------------------------------------------- /test/services/geocode_tests.dart: -------------------------------------------------------------------------------- 1 | import 'package:open_route_service/open_route_service.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | void geocodeTests({ 5 | required OpenRouteService service, 6 | String geocodeQueryText = 'Namibian Brewery', 7 | String geocodeLocalityQueryText = 'Paris', 8 | ORSCoordinate geocodeReversePoint = 9 | const ORSCoordinate(longitude: 2.294471, latitude: 48.858268), 10 | String reverseGeocodeQueryLocality = 'Paris', 11 | }) { 12 | test( 13 | 'Do a Geocode Search using [geocodeSearchGet]', 14 | () async { 15 | final GeoJsonFeatureCollection geocodeData = 16 | await service.geocodeSearchGet( 17 | text: geocodeQueryText, 18 | ); 19 | expect(geocodeData.bbox.length, 2); 20 | expect(geocodeData.features.length, greaterThan(0)); 21 | final String label = geocodeData.features.first.properties['label']!; 22 | expect(label.contains(geocodeQueryText), true); 23 | }, 24 | ); 25 | test( 26 | 'Do a Geocode Search using [geocodeSearchGet], for all layers.', 27 | () async { 28 | final GeoJsonFeatureCollection geocodeData = 29 | await service.geocodeSearchGet( 30 | text: geocodeQueryText, 31 | layers: service.geocodeLayersAvailable.toList(), 32 | ); 33 | expect(geocodeData.bbox.length, 2); 34 | expect(geocodeData.features.length, greaterThan(0)); 35 | final String label = geocodeData.features.first.properties['label']!; 36 | expect(label.contains(geocodeQueryText), true); 37 | }, 38 | ); 39 | test( 40 | 'Do a Geocode Autocomplete query using [geocodeAutoCompleteGet], for all layers.', 41 | () async { 42 | final GeoJsonFeatureCollection geocodeData = 43 | await service.geocodeAutoCompleteGet( 44 | text: geocodeQueryText, 45 | layers: service.geocodeLayersAvailable.toList(), 46 | ); 47 | expect(geocodeData.bbox.length, 2); 48 | expect(geocodeData.features.length, greaterThan(0)); 49 | final String label = geocodeData.features.first.properties['label']!; 50 | expect(label.contains(geocodeQueryText), true); 51 | }, 52 | ); 53 | test( 54 | 'Do a Geocode Structured Search using [geocodeSearchStructuredGet], for all layers.', 55 | () async { 56 | final GeoJsonFeatureCollection geocodeData = 57 | await service.geocodeSearchStructuredGet( 58 | locality: geocodeLocalityQueryText, 59 | layers: service.geocodeLayersAvailable.toList(), 60 | ); 61 | expect(geocodeData.bbox.length, 2); 62 | expect(geocodeData.features.length, greaterThan(0)); 63 | final String label = geocodeData.features.first.properties['label']!; 64 | expect(label.contains(geocodeLocalityQueryText), true); 65 | }, 66 | ); 67 | test( 68 | 'Do a Reverse Geocode using [geocodeReverseGet], for all layers.', 69 | () async { 70 | final GeoJsonFeatureCollection reverseGeocodeData = 71 | await service.geocodeReverseGet( 72 | point: geocodeReversePoint, 73 | layers: service.geocodeLayersAvailable.toList(), 74 | ); 75 | expect(reverseGeocodeData.bbox.length, 2); 76 | expect(reverseGeocodeData.features.length, greaterThan(0)); 77 | final String label = 78 | reverseGeocodeData.features.first.properties['locality']!; 79 | expect(label.contains(reverseGeocodeQueryLocality), true); 80 | }, 81 | ); 82 | test( 83 | 'Cross-validate geocode [geocodeSearchStructuredGet] and reverse geocode [geocodeReverseGet]', 84 | () async { 85 | // First geocode geocodeLocalityQueryText with [geocodeSearchStructuredGet] 86 | final GeoJsonFeatureCollection geocodeData = 87 | await service.geocodeSearchStructuredGet( 88 | locality: geocodeLocalityQueryText, 89 | layers: service.geocodeLayersAvailable.toList(), 90 | ); 91 | final ORSCoordinate geocodedCoordinate = 92 | geocodeData.features.first.geometry.coordinates.first.first; 93 | 94 | // Now, reverse geocode the coordinate received 95 | // from geocoding geocodeLocalityQueryText. 96 | final GeoJsonFeatureCollection reverseGeocodeData = 97 | await service.geocodeReverseGet( 98 | point: geocodedCoordinate, 99 | layers: service.geocodeLayersAvailable.toList(), 100 | ); 101 | final String reverseGeocodedLocality = 102 | reverseGeocodeData.features.first.properties['locality']!; 103 | 104 | // The reverseGeocodedLocality should be similar to originally geocoded 105 | // geocodeLocalityQueryText from which we extracted the coordinate. 106 | expect(reverseGeocodedLocality.contains(geocodeLocalityQueryText), true); 107 | }, 108 | ); 109 | } 110 | -------------------------------------------------------------------------------- /lib/src/models/matrix_data_models.dart: -------------------------------------------------------------------------------- 1 | import 'package:open_route_service/open_route_service.dart'; 2 | 3 | /// A class that encapsulates the matrix of travel times between sources and 4 | /// destinations. 5 | /// 6 | /// Includes the travel time [durations] between each pair of points, the 7 | /// [destinations] and the [sources]. 8 | /// 9 | /// https://openrouteservice.org/dev/#/api-docs/matrix 10 | class TimeDistanceMatrix { 11 | const TimeDistanceMatrix({ 12 | required this.durations, 13 | required this.distances, 14 | required this.destinations, 15 | required this.sources, 16 | }); 17 | 18 | /// Generate a [TimeDistanceMatrix] of travel times between the sources and 19 | /// destinations from a [Map] having keys 'durations', 'destinations', 20 | /// and 'sources'. 21 | factory TimeDistanceMatrix.fromJson(Map json) => 22 | TimeDistanceMatrix( 23 | durations: ((json['durations'] as List?) ?? []) 24 | .map>( 25 | (dynamic duration) => (duration as List) 26 | .map((dynamic d) => d as double) 27 | .toList(), 28 | ) 29 | .toList(), 30 | distances: ((json['distances'] as List?) ?? []) 31 | .map>( 32 | (dynamic duration) => (duration as List) 33 | .map((dynamic d) => d as double) 34 | .toList(), 35 | ) 36 | .toList(), 37 | destinations: ((json['destinations'] as List?) ?? []) 38 | .map( 39 | (dynamic destination) => 40 | TimeDistanceMatrixLocation.fromJson(destination), 41 | ) 42 | .toList(), 43 | sources: ((json['sources'] as List?) ?? []) 44 | .map( 45 | (dynamic source) => TimeDistanceMatrixLocation.fromJson(source)) 46 | .toList(), 47 | ); 48 | 49 | /// The travel times of the Matrix Routes 50 | final List> durations; 51 | 52 | /// The destinations of the Matrix Routes 53 | final List> distances; 54 | 55 | /// The destination values of the Matrix. 56 | final List destinations; 57 | 58 | /// The source values of the Matrix. 59 | final List sources; 60 | 61 | /// Converts the [TimeDistanceMatrix] to a [Map] having [String] keys 62 | /// 'distances', 'durations', 'destinations', and 'sources'. 63 | Map toJson() => { 64 | 'distances': distances, 65 | 'durations': durations, 66 | 'destinations': destinations 67 | .map>( 68 | (TimeDistanceMatrixLocation e) => e.toJson()) 69 | .toList(), 70 | 'sources': sources 71 | .map>( 72 | (TimeDistanceMatrixLocation e) => e.toJson()) 73 | .toList(), 74 | }; 75 | 76 | @override 77 | String toString() => toJson().toString(); 78 | 79 | @override 80 | bool operator ==(Object other) => 81 | other is TimeDistanceMatrix && 82 | distances == other.distances && 83 | destinations == other.destinations && 84 | sources == other.sources && 85 | durations == other.durations; 86 | 87 | @override 88 | int get hashCode => toJson().hashCode; 89 | } 90 | 91 | /// A class that denotes a source or destination location data in a Matrix. 92 | /// 93 | /// Includes the [snappedDistance] and [location], the [ORSCoordinate] 94 | /// of the location. 95 | class TimeDistanceMatrixLocation { 96 | const TimeDistanceMatrixLocation( 97 | {required this.snappedDistance, required this.location}); 98 | 99 | /// Generates a [TimeDistanceMatrixLocation] from a [Map] having [String] keys 100 | /// 'latitude' and 'longitude', respectively having [latitude] and [longitude] 101 | /// as [double] values. 102 | factory TimeDistanceMatrixLocation.fromJson(Map json) => 103 | TimeDistanceMatrixLocation( 104 | snappedDistance: json['snapped_distance'], 105 | location: ORSCoordinate.fromList(json['location'] as List), 106 | ); 107 | 108 | /// The snapped distance of the location. 109 | final double snappedDistance; 110 | 111 | /// The coordinate of the location. 112 | final ORSCoordinate location; 113 | 114 | /// Generates a [Map] having [String] keys 'snapped_distance' and 'location' 115 | /// which has [longitude] and [latitude] as a [List] of [double] values. 116 | Map toJson() => { 117 | 'snapped_distance': snappedDistance, 118 | 'location': [location.longitude, location.latitude], 119 | }; 120 | 121 | @override 122 | String toString() => toJson().toString(); 123 | 124 | @override 125 | bool operator ==(Object other) => 126 | other is TimeDistanceMatrixLocation && 127 | other.snappedDistance == snappedDistance && 128 | other.location == location; 129 | 130 | @override 131 | int get hashCode => snappedDistance.hashCode ^ location.hashCode; 132 | } 133 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Releases 2 | 3 | ## [1.2.7] - 2nd February, 2025 4 | 5 | - Enhance OpenRouteService class with improved error handling and response parsing. 6 | - Refactored error handling in matrix and POIs services to use updated exceptions 7 | - Added a `close` method to the `OpenRouteService` class to close the underlying HTTP client. 8 | 9 | ## [1.2.6] - 6th April, 2024 10 | 11 | - Empty checks to prevent breaking when receiving empty collections from openrouteservice. Fixes [Issue #21](https://github.com/Dhi13man/open_route_service/issues/21). 12 | - Unit Tests for empty GeoJSON Feature serialisation/deserialisation added. 13 | 14 | ## [1.2.5] - 29th March, 2024 15 | 16 | - Fixed broken compatibility with `geodart` GeoJSON serialisation/deserialisation as reported in [Issue #19](https://github.com/Dhi13man/open_route_service/issues/19). 17 | - Using `geojson` package as a dev dependency, for unit testing compatibility with GeoJSON. 18 | 19 | ## [1.2.4] - 6th Feb, 2024 20 | 21 | - **MINOR BREAKING:** Removed getter and setter for `profile` in `OpenRouteService` class. It is now final and can only be set via the constructor `defaultProfile` parameter. This system is more concurrency-safe. If it needs to be overridden at the API call level, that can anyway be done by passing in `profileOverride` to the respective API method. 22 | 23 | - Made API more flexible by allowing constructor-based overrides in `OpenRouteService` class of: 24 | - `baseUrl`, the default API Base URL 25 | - `client`, the HTTP Client being used to make the requests 26 | - `defaultProfile`, the default openrouteservice route profile being used to make the requests. 27 | 28 | - Ternary operator null check replaced with null-aware operator in Vroom and Optimization data models. 29 | 30 | - Getting `ORSProfile` from `String` name and vice versa is now done using a map internally, making it more readable and maintainable. 31 | 32 | ## [1.2.3] - 24th June, 2023 33 | 34 | - Upgraded dependencies to keep up with the latest versions of the packages. 35 | 36 | ## [1.2.2] - 15th September, 2022 37 | 38 | - Matrix Data Models Null Safety Fixes as per [[#12](https://github.com/Dhi13man/open_route_service/issues/12)]. 39 | 40 | ## [1.2.1] - 10th February, 2022 41 | 42 | - Better Response Status-Code verification logic as for posts it won't always be 200. 43 | 44 | - **BREAKING:** Separated and refined openrouteservice path-profile String and ORSProfile interconversion logic. 45 | `OpenRouteService.getProfileString(ORSProfile profile)` -> `profile.name` 46 | 47 | - **Minor:** Dev Dependencies updated. Pubspec improved. 48 | 49 | ## [1.1.1] - 06th February, 2022 50 | 51 | - Optimize imports as per static analysis. 52 | 53 | ## [1.1.0] - 14th November, 2021 54 | 55 | - **BREAKING:** Renamed `OpenRouteService` methods with even stricter naming convention: 56 | ```{API endpoint name} + {functionality name} + Post / Get (Based on type of Request)``` 57 | 58 | - **BREAKING:** `Coordinate` Model renamed to `ORSCoordinate` to avoid ambiguity with other location based packages. 59 | 60 | - `ORSCoordinate.fromJSON()` fixes for errors with altitude: potential null error and wrong key fixed. 61 | 62 | ## [1.0.1] - 31st October, 2021 63 | 64 | - Restriction applied to `request` parameter of `ORSPois.poisDataPostGet` with `ArgumentError` if restriction not followed, as per API convention. `request` can now only be 'pois', 'stats' or 'list' as per openrouteservice API. 65 | 66 | - Linted Code. :) 67 | 68 | ## [1.0.0] - 30th October, 2021 69 | 70 | - First Stable Release (unless there is some breaking bug that slipped by). 71 | 72 | - Addition of GeoCoding API. 73 | 74 | - Metadata models. 75 | 76 | - **BREAKING:** GeoJsonFeatureProperties Data Model removed and replaced with unparsed `Map` as it doesn't have any static structure across various endpoints. 77 | 78 | - **BREAKING:** Every method in the package has been renamed for consistency and to easily find needed methods. New method naming convention: 79 | 80 | ```{API endpoint name} + {functionality name} + (if functionality uses a POST endpoint) Post + Get``` 81 | 82 | Eg. `getRouteCoordinates -> directionsRouteCoordsGet`, `getElevationDataPost -> elevationDataPostGet` and so on. 83 | 84 | ## [0.7.0] - 27th September, 2021 85 | 86 | - **BREAKING:** `Matrix*` -> `TimeDistanceMatrix*` 87 | 88 | - Adjust cgiar attribution link from http to https 89 | 90 | - `Coordinate` model `toList` and `fromList` methods added for convenience (with null safety). 91 | 92 | - Documentation updates. 93 | 94 | - Encapsulated `Optimization` API. 95 | 96 | ## [0.6.0] - 26th September, 2021 97 | 98 | - **BREAKING:** Naming conventions changed: `OpenRouteService*` -> `ORS*`. 99 | 100 | - Encapsulated `Matrix` API. 101 | 102 | - Encapsulated `POIs` API. 103 | 104 | ## [0.5.2] - 26th September, 2021 105 | 106 | - Reworked the entire `Directions` API system to enable usage of both the normal POST endpoint as `getMultiRouteDirectionsData` and the geojson POST endpoint `getMultiRouteDirectionsGeoJson`. 107 | 108 | - Common `GeoJsonFeatureCollection` Data Model created to be used with both the `Directions` API and the `Isochrones` API, whenever geojson is involved. 109 | 110 | ## [0.5.1] - 26th September, 2021 111 | 112 | - Dart SDK version change to pass static analysis on pub.dev. 113 | 114 | - Ran `dart format` on all Dart files to be in compliance with Dart's style guide. 115 | 116 | ## [0.5.0] - 26th September, 2021 117 | 118 | - Initial version. 119 | 120 | - APIs of OpenRouteService currently encapsulated and available: 121 | 1. [Directions](https://openrouteservice.org/dev/#/api-docs/v2/directions/) 122 | 2. [Elevation](https://openrouteservice.org/dev/#/api-docs/elevation/) 123 | 3. [Isochrones](https://openrouteservice.org/dev/#/api-docs/v2/isochrones/) 124 | 125 | - Tests Ready for the APIs too. 126 | -------------------------------------------------------------------------------- /lib/src/open_route_service_base.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:http/http.dart' as http; 4 | import 'package:open_route_service/src/exceptions/base.dart'; 5 | import 'package:open_route_service/src/exceptions/matrix.dart'; 6 | import 'package:open_route_service/src/exceptions/pois.dart'; 7 | 8 | import 'package:open_route_service/src/models/coordinate_model.dart'; 9 | import 'package:open_route_service/src/models/direction_data_models.dart'; 10 | import 'package:open_route_service/src/models/elevation_data_models.dart'; 11 | import 'package:open_route_service/src/models/geojson_feature_models.dart'; 12 | import 'package:open_route_service/src/models/matrix_data_models.dart'; 13 | import 'package:open_route_service/src/models/optimization_models/optimization_data_models.dart'; 14 | import 'package:open_route_service/src/models/optimization_models/vroom_data_models.dart'; 15 | import 'package:open_route_service/src/models/pois_data_models.dart'; 16 | 17 | part 'package:open_route_service/src/models/ors_profile_enum.dart'; 18 | part 'package:open_route_service/src/services/directions.dart'; 19 | part 'package:open_route_service/src/services/elevation.dart'; 20 | part 'package:open_route_service/src/services/geocode.dart'; 21 | part 'package:open_route_service/src/services/isochrones.dart'; 22 | part 'package:open_route_service/src/services/matrix.dart'; 23 | part 'package:open_route_service/src/services/optimization.dart'; 24 | part 'package:open_route_service/src/services/pois.dart'; 25 | 26 | /// Class encapsulating all the OpenRoute Service APIs and parsing their 27 | /// responses into relevant data models that can be easily integrated into any 28 | /// Dart/Flutter application. 29 | /// 30 | /// Initialize the class with your API key [String] and optionally the 31 | /// [ORSProfile], then use the methods to get the data you need. 32 | /// 33 | /// Implemented OpenRoute APIs include: 34 | /// - Directions: [ORSDirections] 35 | /// - Elevation: [ORSElevation] 36 | /// - Geocoding: [ORSGeocode] 37 | /// - Isochrones: [ORServiceIsochrones] 38 | /// - Matrix: [ORSMatrix] 39 | /// - Optimization: [ORSOptimization] 40 | /// - POIs: [ORSPois] 41 | /// 42 | /// The API documentation can be found here: 43 | /// https://openrouteservice.org/dev/#/api-docs 44 | class OpenRouteService { 45 | /// Constructor. 46 | /// 47 | /// - [apiKey]: API key for authentication. 48 | /// - [baseUrl]: Base URL of the endpoints (defaults to [defaultBaseUrl]). 49 | /// - [client]: Optional HTTP client; if not provided, a new instance is created. 50 | /// - [defaultProfile]: Default routing profile. 51 | OpenRouteService({ 52 | required String apiKey, 53 | String baseUrl = OpenRouteService.defaultBaseUrl, 54 | http.Client? client, 55 | ORSProfile defaultProfile = ORSProfile.footWalking, 56 | }) : _apiKey = apiKey, 57 | _baseUrl = baseUrl, 58 | _defaultProfile = defaultProfile { 59 | // Initialize HTTP client if not provided. 60 | _client = client ?? http.Client(); 61 | } 62 | 63 | /// API key used for authentication. 64 | final String _apiKey; 65 | 66 | /// The base URL of all the endpoints. 67 | /// Defaults to [defaultBaseUrl]. 68 | final String _baseUrl; 69 | 70 | /// HTTP Client used to persistently make the request. 71 | late final http.Client _client; 72 | 73 | /// The path parameter determines the routing method. 74 | final ORSProfile _defaultProfile; 75 | 76 | /// The default base URL of all the endpoints, https://api.openrouteservice.org 77 | static const String defaultBaseUrl = 'https://api.openrouteservice.org'; 78 | 79 | /// Allows closing the HTTP client when done. 80 | void close() { 81 | _client.close(); 82 | } 83 | 84 | /// Performs a GET request on the OpenRouteService API endpoint. 85 | /// 86 | /// - [uri]: The full URI for the API call. 87 | /// 88 | /// Returns a [Future] which resolves with the decoded response body. 89 | /// Throws an [ORSHttpException] if the request fails. 90 | Future _openRouteServiceGet({required Uri uri}) async { 91 | // Fetch the data. 92 | final http.Response response = await _client.get(uri); 93 | return _extractDecodedResponse(response, uri); 94 | } 95 | 96 | /// Performs a POST request on the OpenRouteService API endpoint. 97 | /// 98 | /// - [uri]: The full URI for the API call. 99 | /// - [data]: Request body to be sent in JSON format. 100 | /// 101 | /// Returns a [Future] which resolves with the decoded response body. 102 | /// Throws an [ORSHttpException] if the request fails. 103 | Future _openRouteServicePost({ 104 | required Uri uri, 105 | required Map data, 106 | }) async { 107 | // Fetch the data. 108 | final http.Response response = await _client.post( 109 | uri, 110 | body: jsonEncode(data), 111 | headers: { 112 | 'Accept': 113 | 'application/json, application/geo+json, application/gpx+xml, img/png; charset=utf-8', 114 | 'Authorization': _apiKey, 115 | 'Content-Type': 'application/json; charset=utf-8', 116 | }, 117 | ); 118 | return _extractDecodedResponse(response, uri); 119 | } 120 | 121 | /// Extracts and returns the decoded response. 122 | /// 123 | /// - [response]: The HTTP response. 124 | /// 125 | /// Returns the decoded response body if the status code is 2xx. 126 | /// Throws an [ORSHttpException] if the status code is not 2xx. 127 | dynamic _extractDecodedResponse(http.Response response, Uri uri) { 128 | final dynamic parsedResponse = _parseResponse(response, uri); 129 | if (response.statusCode >= 200 && response.statusCode < 300) { 130 | return parsedResponse; 131 | } else { 132 | throw ORSHttpException( 133 | uri: uri, 134 | statusCode: response.statusCode, 135 | errorResponse: parsedResponse, 136 | ); 137 | } 138 | } 139 | 140 | /// Parses the HTTP [response] based on its content type. 141 | /// 142 | /// - [response]: The HTTP response. 143 | /// 144 | /// Returns parsed JSON if the response contains 'application/json', otherwise the raw body. 145 | /// Throws an [ORSParsingException] if parsing fails. 146 | dynamic _parseResponse(http.Response response, Uri uri) { 147 | try { 148 | final String contentType = response.headers['content-type'] ?? ''; 149 | return contentType.contains('json') 150 | ? jsonDecode(response.body) 151 | : response.body; 152 | } catch (e, trace) { 153 | throw ORSParsingException( 154 | uri: uri, 155 | cause: e is Exception ? e : Exception(e.toString()), 156 | causeStackTrace: trace, 157 | ); 158 | } 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /lib/src/models/direction_data_models.dart: -------------------------------------------------------------------------------- 1 | import 'package:open_route_service/open_route_service.dart'; 2 | 3 | /// A class that encapsulates Direction Route data received from the JSON 4 | /// endpoint of Directions API. 5 | /// 6 | /// Includes the Route's [summary], [segments], [bbox], [geometry] 7 | /// and [waypoints]. 8 | /// 9 | /// https://openrouteservice.org/dev/#/api-docs/v2/directions/{profile}/post 10 | class DirectionRouteData { 11 | const DirectionRouteData({ 12 | required this.summary, 13 | required this.segments, 14 | required this.bbox, 15 | required this.geometry, 16 | required this.wayPoints, 17 | }); 18 | 19 | /// Generates a [DirectionRouteData] from a received [Map] having the keys 20 | /// 'summary', 'segments', 'bbox', 'geometry', and 'way_points'. 21 | factory DirectionRouteData.fromJson(Map json) => 22 | DirectionRouteData( 23 | summary: DirectionRouteSummary.fromJson(json['summary']), 24 | segments: (json['segments'] as List) 25 | .map( 26 | (dynamic segment) => DirectionRouteSegment.fromJson(segment), 27 | ) 28 | .toList(), 29 | bbox: [ 30 | ORSCoordinate(longitude: json['bbox'][0], latitude: json['bbox'][1]), 31 | ORSCoordinate(longitude: json['bbox'][2], latitude: json['bbox'][3]) 32 | ], 33 | geometry: json['geometry'] as String, 34 | wayPoints: (json['way_points'] as List) 35 | .map((dynamic e) => (e as num).toDouble()) 36 | .toList(), 37 | ); 38 | 39 | /// The summary of the route. 40 | final DirectionRouteSummary summary; 41 | 42 | /// The list of [DirectionRouteSegment]s comprising the route. 43 | final List segments; 44 | 45 | /// The bounding box covering the route. 46 | final List bbox; 47 | 48 | /// The geometry of the route as encoded polyline. 49 | final String geometry; 50 | 51 | /// The list of waypoints marking the route. 52 | final List wayPoints; 53 | 54 | /// Converts the [DirectionRouteData] to a [Map] with keys 'summary', 55 | /// 'segments', 'bbox', 'geometry', and 'way_points'. 56 | /// 57 | /// The 'bbox' key is converted to a list of 4 [double]s implying 58 | /// 2 coordinates. 59 | Map toJson() => { 60 | 'summary': summary.toJson(), 61 | 'segments': segments 62 | .map>( 63 | (DirectionRouteSegment segment) => segment.toJson(), 64 | ) 65 | .toList(), 66 | 'bbox': [ 67 | bbox[0].longitude, 68 | bbox[0].latitude, 69 | bbox[1].longitude, 70 | bbox[1].latitude, 71 | ], 72 | 'geometry': geometry, 73 | 'way_points': wayPoints, 74 | }; 75 | 76 | @override 77 | String toString() => toJson().toString(); 78 | } 79 | 80 | /// A class that encapsulates the summary of a [DirectionRouteData]. 81 | /// 82 | /// Includes the [distance] and [duration] for travelling the route. 83 | class DirectionRouteSummary { 84 | const DirectionRouteSummary({ 85 | required this.distance, 86 | required this.duration, 87 | }); 88 | 89 | /// Generates a [DirectionRouteSummary] from a received [Map] having the keys 90 | /// 'distance' and 'duration'. 91 | factory DirectionRouteSummary.fromJson(Map json) => 92 | DirectionRouteSummary( 93 | distance: json['distance'] as double, 94 | duration: json['duration'] as double, 95 | ); 96 | 97 | /// The distance of the route. 98 | final double distance; 99 | 100 | /// The duration required to travel the route. 101 | final double duration; 102 | 103 | /// Converts the [DirectionRouteSummary] to [Map] with keys 104 | /// 'distance' and 'duration'. 105 | Map toJson() => { 106 | 'distance': distance, 107 | 'duration': duration, 108 | }; 109 | 110 | @override 111 | String toString() => toJson().toString(); 112 | } 113 | 114 | /// A class that encapsulates a segment of a [DirectionRouteData]. 115 | /// 116 | /// Includes the [distance], [duration] and a [List] of 117 | /// [DirectionRouteSegmentStep] ([steps]) for travelling the segment. 118 | class DirectionRouteSegment { 119 | const DirectionRouteSegment({ 120 | required this.distance, 121 | required this.duration, 122 | required this.steps, 123 | }); 124 | 125 | /// Generates a [DirectionRouteSegment] from a received [Map] having the keys 126 | /// 'distance', 'duration' and 'steps'. 127 | factory DirectionRouteSegment.fromJson(Map json) => 128 | DirectionRouteSegment( 129 | distance: json['distance'] as double, 130 | duration: json['duration'] as double, 131 | steps: (json['steps'] as List) 132 | .map( 133 | (dynamic step) => DirectionRouteSegmentStep.fromJson(step), 134 | ) 135 | .toList(), 136 | ); 137 | 138 | /// The distance of the route segment. 139 | final double distance; 140 | 141 | /// The duration required to travel the route segment. 142 | final double duration; 143 | 144 | /// The list of [DirectionRouteSegmentStep]s that need to be taken to travel 145 | /// the route segment. 146 | final List steps; 147 | 148 | /// Converts the [DirectionRouteSegment] to a [Map] with keys 'distance', 149 | /// 'duration' and 'steps'. 150 | Map toJson() => { 151 | 'distance': distance, 152 | 'duration': duration, 153 | 'steps': steps 154 | .map>( 155 | (DirectionRouteSegmentStep step) => step.toJson(), 156 | ) 157 | .toList(), 158 | }; 159 | 160 | @override 161 | String toString() => toJson().toString(); 162 | } 163 | 164 | /// A class that encapsulates one step of all the steps need to travel 165 | /// a [DirectionRouteSegment]. 166 | /// 167 | /// Includes the [distance], [duration] and [instruction] for travelling 168 | /// the [DirectionRouteSegment]'s step with its [name], as well as the [type], 169 | /// and [wayPoints] for the step. 170 | class DirectionRouteSegmentStep { 171 | const DirectionRouteSegmentStep({ 172 | required this.distance, 173 | required this.duration, 174 | required this.type, 175 | required this.instruction, 176 | required this.name, 177 | required this.wayPoints, 178 | }); 179 | 180 | /// Generates a [DirectionRouteSegmentStep] from a received [Map] having the 181 | /// keys 'distance', 'duration', 'type', 'instruction', 'name', 'way_points'. 182 | factory DirectionRouteSegmentStep.fromJson(Map json) => 183 | DirectionRouteSegmentStep( 184 | distance: json['distance'] as double, 185 | duration: json['duration'] as double, 186 | type: json['type'] as int, 187 | instruction: json['instruction'] as String, 188 | name: json['name'] as String, 189 | wayPoints: (json['way_points'] as List) 190 | .map((dynamic e) => (e as num).toDouble()) 191 | .toList(), 192 | ); 193 | 194 | /// The distance of the route segment step. 195 | final double distance; 196 | 197 | /// The duration required to travel the route segment step. 198 | final double duration; 199 | 200 | /// The type of the route segment step. 201 | final int type; 202 | 203 | /// The instruction needed to be followed to travel the route segment step. 204 | final String instruction; 205 | 206 | /// The name of the route segment step. 207 | final String name; 208 | 209 | /// The list of waypoints marking the route segment step. 210 | final List wayPoints; 211 | 212 | /// Converts the [DirectionRouteSegmentStep] to a [Map] with keys 'distance', 213 | /// 'duration', 'type', 'instruction', 'name', and 'way_points'. 214 | Map toJson() => { 215 | 'distance': distance, 216 | 'duration': duration, 217 | 'type': type, 218 | 'instruction': instruction, 219 | 'name': name, 220 | 'way_points': wayPoints, 221 | }; 222 | 223 | @override 224 | String toString() => toJson().toString(); 225 | } 226 | -------------------------------------------------------------------------------- /test/miscellaneous/geojson_tests.dart: -------------------------------------------------------------------------------- 1 | import 'package:geodart/geometries.dart'; 2 | import 'package:open_route_service/open_route_service.dart'; 3 | import 'package:test/test.dart'; 4 | 5 | void geoJsonTests() { 6 | test('Test GeoJSON Feature Collection serialization', () { 7 | // Arrange 8 | final List coordinates = [ 9 | ORSCoordinate(latitude: 3.0, longitude: 0.0), 10 | ORSCoordinate(latitude: 3.0, longitude: 0.0) 11 | ]; 12 | final GeoJsonFeatureCollection feature = GeoJsonFeatureCollection( 13 | bbox: coordinates, 14 | features: [], 15 | ); 16 | 17 | // Act 18 | final Map result = feature.toJson(); 19 | 20 | // Assert 21 | expect( 22 | result, 23 | { 24 | 'type': 'FeatureCollection', 25 | 'bbox': [0.0, 3.0, 0.0, 3.0], 26 | 'features': >[] 27 | }, 28 | ); 29 | }); 30 | 31 | test('Test GeoJSON Feature Collection deserialization', () { 32 | // Arrange 33 | final Map json = { 34 | 'type': 'FeatureCollection', 35 | 'bbox': [0.0, 3.0, 0.0, 1.5], 36 | 'features': >[] 37 | }; 38 | 39 | // Act 40 | final GeoJsonFeatureCollection result = 41 | GeoJsonFeatureCollection.fromJson(json); 42 | 43 | // Assert 44 | final GeoJsonFeatureCollection expected = GeoJsonFeatureCollection( 45 | bbox: [ 46 | ORSCoordinate(latitude: 3.0, longitude: 0.0), 47 | ORSCoordinate(latitude: 1.5, longitude: 0.0) 48 | ], 49 | features: [], 50 | ); 51 | expect(result.bbox, expected.bbox); 52 | expect(result.features, expected.features); 53 | }); 54 | 55 | test('Test empty GeoJSON Feature Collection serialization', () { 56 | // Arrange 57 | final GeoJsonFeatureCollection feature = GeoJsonFeatureCollection( 58 | bbox: [], 59 | features: [], 60 | ); 61 | 62 | // Act 63 | final Map result = feature.toJson(); 64 | 65 | // Assert 66 | expect( 67 | result, 68 | { 69 | 'type': 'FeatureCollection', 70 | 'bbox': [], 71 | 'features': >[], 72 | }, 73 | ); 74 | }); 75 | 76 | test('Test empty GeoJSON Feature Collection deserialization', () { 77 | // Arrange 78 | final Map json = { 79 | 'type': 'FeatureCollection', 80 | 'bbox': [], 81 | 'features': >[], 82 | }; 83 | 84 | // Act 85 | final GeoJsonFeatureCollection result = 86 | GeoJsonFeatureCollection.fromJson(json); 87 | 88 | // Assert 89 | final GeoJsonFeatureCollection expected = GeoJsonFeatureCollection( 90 | bbox: [], 91 | features: [], 92 | ); 93 | expect(result.bbox, expected.bbox); 94 | expect(result.features, expected.features); 95 | }); 96 | 97 | test('Test GeoJSON Point Coordinate serialization', () { 98 | // Arrange 99 | final List coordinates = [ 100 | ORSCoordinate(latitude: 1.5, longitude: 0.0) 101 | ]; 102 | final GeoJsonFeature feature = GeoJsonFeature( 103 | type: 'Feature', 104 | geometry: GeoJsonFeatureGeometry( 105 | coordinates: >[coordinates], 106 | type: 'Point', 107 | internalType: GsonFeatureGeometryCoordinatesType.list, 108 | ), 109 | properties: {}, 110 | ); 111 | 112 | // Act 113 | final Map result = feature.toJson(); 114 | 115 | // Assert 116 | expect( 117 | result, 118 | { 119 | 'type': 'Feature', 120 | 'geometry': { 121 | 'type': 'Point', 122 | 'coordinates': >[ 123 | [0.0, 1.5] 124 | ] 125 | }, 126 | 'properties': {} 127 | }, 128 | ); 129 | }); 130 | 131 | test('Test GeoJSON Point Coordinate deserialization', () { 132 | // Arrange 133 | final Map json = { 134 | 'type': 'Feature', 135 | 'geometry': { 136 | 'type': 'Point', 137 | 'coordinates': >[ 138 | [0.0, 1.5] 139 | ] 140 | }, 141 | 'properties': {} 142 | }; 143 | 144 | // Act 145 | final GeoJsonFeature result = GeoJsonFeature.fromJson(json); 146 | 147 | // Assert 148 | final List coordinates = [ 149 | ORSCoordinate(longitude: 0.0, latitude: 1.5) 150 | ]; 151 | final GeoJsonFeature expected = GeoJsonFeature( 152 | type: 'Feature', 153 | geometry: GeoJsonFeatureGeometry( 154 | coordinates: >[coordinates], 155 | type: 'Point', 156 | internalType: GsonFeatureGeometryCoordinatesType.list, 157 | ), 158 | properties: {}, 159 | ); 160 | expect(result.bbox, expected.bbox); 161 | expect(result.properties, expected.properties); 162 | expect(result.type, expected.type); 163 | expect(result.geometry.internalType, expected.geometry.internalType); 164 | expect(result.geometry.type, expected.geometry.type); 165 | for (int i = 0; i < result.geometry.coordinates.length; i++) { 166 | for (int j = 0; j < result.geometry.coordinates[i].length; j++) { 167 | expect( 168 | result.geometry.coordinates[i][j].latitude, 169 | expected.geometry.coordinates[i][j].latitude, 170 | ); 171 | expect( 172 | result.geometry.coordinates[i][j].longitude, 173 | expected.geometry.coordinates[i][j].longitude, 174 | ); 175 | } 176 | } 177 | }); 178 | 179 | test('Test GeoJSON Point Coordinate serialization and deserialization', () { 180 | // Arrange 181 | final Point original = Point(Coordinate(51.5, 0.0)); 182 | 183 | // Act 184 | final Map jsonPoint = original.toJson(); 185 | final GeoJsonFeature feature = GeoJsonFeature.fromJson(jsonPoint); 186 | final Map featureJson = feature.toJson(); 187 | final Point result = Point.fromJson(featureJson); 188 | 189 | // Assert 190 | expect(result.bbox.center, original.bbox.center); 191 | expect(result.bbox.maxLat, original.bbox.maxLat); 192 | expect(result.bbox.maxLong, original.bbox.maxLong); 193 | expect(result.bbox.minLat, original.bbox.minLat); 194 | expect(result.bbox.minLong, original.bbox.minLong); 195 | expect(result.properties, original.properties); 196 | }); 197 | 198 | test('Test empty GeoJSON Coordinates serialization', () { 199 | // Arrange 200 | final GeoJsonFeature feature = GeoJsonFeature( 201 | type: 'Feature', 202 | geometry: GeoJsonFeatureGeometry( 203 | coordinates: >[], 204 | type: 'Point', 205 | internalType: GsonFeatureGeometryCoordinatesType.empty, 206 | ), 207 | properties: {}, 208 | ); 209 | 210 | // Act 211 | final Map result = feature.toJson(); 212 | 213 | // Assert 214 | expect( 215 | result, 216 | { 217 | 'type': 'Feature', 218 | 'geometry': { 219 | 'type': 'Point', 220 | 'coordinates': >[] 221 | }, 222 | 'properties': {} 223 | }, 224 | ); 225 | }); 226 | 227 | test('Test empty GeoJSON Coordinate deserialization', () { 228 | // Arrange 229 | final Map json = { 230 | 'type': 'Feature', 231 | 'geometry': { 232 | 'type': 'Point', 233 | 'coordinates': >[] 234 | }, 235 | 'properties': {} 236 | }; 237 | 238 | // Act 239 | final GeoJsonFeature result = GeoJsonFeature.fromJson(json); 240 | 241 | // Assert 242 | final GeoJsonFeature expected = GeoJsonFeature( 243 | type: 'Feature', 244 | geometry: GeoJsonFeatureGeometry( 245 | coordinates: >[], 246 | type: 'Point', 247 | internalType: GsonFeatureGeometryCoordinatesType.empty, 248 | ), 249 | properties: {}, 250 | ); 251 | expect(result.bbox, expected.bbox); 252 | expect(result.properties, expected.properties); 253 | expect(result.type, expected.type); 254 | expect(result.geometry.internalType, expected.geometry.internalType); 255 | expect(result.geometry.type, expected.geometry.type); 256 | for (int i = 0; i < result.geometry.coordinates.length; i++) { 257 | for (int j = 0; j < result.geometry.coordinates[i].length; j++) { 258 | expect( 259 | result.geometry.coordinates[i][j].latitude, 260 | expected.geometry.coordinates[i][j].latitude, 261 | ); 262 | expect( 263 | result.geometry.coordinates[i][j].longitude, 264 | expected.geometry.coordinates[i][j].longitude, 265 | ); 266 | } 267 | } 268 | }); 269 | } 270 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # open_route_service 2 | 3 | [![License](https://img.shields.io/github/license/dhi13man/open_route_service)](https://github.com/Dhi13man/open_route_service/blob/main/LICENSE) 4 | [![Language](https://img.shields.io/badge/language-Dart-blue.svg)](https://dart.dev) 5 | [![Language](https://img.shields.io/badge/language-Flutter-blue.svg)](https://flutter.dev) 6 | [![Contributors](https://img.shields.io/github/contributors-anon/dhi13man/open_route_service?style=flat)](https://github.com/Dhi13man/open_route_service/graphs/contributors) 7 | [![GitHub forks](https://img.shields.io/github/forks/dhi13man/open_route_service?style=social)](https://github.com/Dhi13man/open_route_service/network/members) 8 | [![GitHub Repo stars](https://img.shields.io/github/stars/dhi13man/open_route_service?style=social)](https://github.com/Dhi13man/open_route_service/stargazers) 9 | [![Last Commit](https://img.shields.io/github/last-commit/dhi13man/open_route_service)](https://github.com/Dhi13man/open_route_service/commits/main) 10 | [![Build, Format, Test](https://github.com/Dhi13man/open_route_service/workflows/Build,%20Format,%20Test/badge.svg)](https://github.com/Dhi13man/open_route_service/actions) 11 | [![open_route_service version](https://img.shields.io/pub/v/open_route_service.svg)](https://pub.dev/packages/open_route_service) 12 | 13 | [!["Buy Me A Coffee"](https://img.buymeacoffee.com/button-api/?text=Buy%20me%20an%20Ego%20boost&emoji=%F0%9F%98%B3&slug=dhi13man&button_colour=FF5F5F&font_colour=ffffff&font_family=Lato&outline_colour=000000&coffee_colour=FFDD00****)](https://www.buymeacoffee.com/dhi13man) 14 | 15 | This package is an encapsulation/wrapper made around [openrouteservice API](https://openrouteservice.org) for Dart and Flutter projects. 16 | 17 | The package enables the easy integration of the openrouteservice API with relevant data models, for generation of Routes and Directions on Maps, Isochrones, Time-Distance Matrix, Pelias Geocoding, POIs, Elevation, routing Optimizations etc, using their amazing API. 18 | 19 | [Contribute to openrouteservice API by donating](https://openrouteservice.org/donations/) to help keep the service free and accessible to everyone. For more information about the API, view the [openrouteservice API documentation](https://openrouteservice.org/dev/#/api-docs). 20 | 21 | ## Contents 22 | 23 | - [open\_route\_service](#open_route_service) 24 | - [Contents](#contents) 25 | - [Features](#features) 26 | - [Getting started](#getting-started) 27 | - [Steps for Usage](#steps-for-usage) 28 | - [Example Usage](#example-usage) 29 | - [Contribution Guidelines](#contribution-guidelines) 30 | - [Dependencies](#dependencies) 31 | - [Additional information](#additional-information) 32 | - [Sponsor Message](#sponsor-message) 33 | 34 | ## Features 35 | 36 | The goal is to develop an all-encompassing package that can encapsulate everything openrouteservice API offers. 37 | 38 | With all of their internal Optimizations, this includes: 39 | 40 | 1. **[Directions](https://openrouteservice.org/dev/#/api-docs/v2/directions/):** 41 | Route Generation between any two or more coordinates for any mode of transportation. For example, from a starting point to a destination on `'foot-walking'`. 42 | 43 | E.g. `ORSDirections.directionsRouteCoordsGet` gives a `List` of `ORSCoordinate` which can then be easily used to draw a Polyline route on a map in a Flutter Application or anything else you can think of. 44 | 45 | | Route Drawn on Flutter App Map using Coordinates | 46 | | ------------------------------------------------ | 47 | | ![Route Drawn on Map (Flutter)][route_img] | 48 | 49 | [route_img]: https://raw.githubusercontent.com/Dhi13man/open_route_service/main/screenshots/directions_map.png 50 | 51 | 2. **[Elevation](https://openrouteservice.org/dev/#/api-docs/elevation/):** 52 | Get the elevation of a coordinate, or a list of coordinates. Fetches the `ElevationData` by taking a 2D `ORSCoordinate` or planar line geometry, and enriching it with elevation from a variety of datasets. 53 | 54 | | Elevation Response Received | 55 | | ------------------------------------------- | 56 | | ![Sample Elevation Response][elevation_img] | 57 | 58 | [elevation_img]: https://raw.githubusercontent.com/Dhi13man/open_route_service/main/screenshots/elevation_response.png 59 | 60 | 3. **[Isochrones](https://openrouteservice.org/dev/#/api-docs/v2/isochrones/):** 61 | Obtain Isochrone (areas of reachability) Data for the locations given. The isochrone is a polygon that encloses a given point and is bounded by a given time. 62 | 63 | The isochrone data can be used to draw them on a map in a Flutter Application, or anything else you can think of. 64 | 65 | | Isochrone Drawn on Map | 66 | | ---------------------------------------- | 67 | | ![Isochrone Drawn on Map][isochrone_img] | 68 | 69 | [isochrone_img]: https://raw.githubusercontent.com/Dhi13man/open_route_service/main/screenshots/isochrone_map.png 70 | 71 | 4. **[Time-Distance Matrix](https://openrouteservice.org/dev/#/api-docs/matrix):** 72 | Obtain one-to-many, many-to-one and many-to-many matrices for time and distance. Returns duration or distance matrix for multiple source and destination points. 73 | 74 | 5. **[Pelias Geocoding](https://openrouteservice.org/dev/#/api-docs/geocode):** 75 | Resolve input coordinates to addresses and vice versa. Provides functionality for geocoding autocomplete queries, search queries, and reverse geocoding. 76 | 77 | | Reverse Geocoding Information used on Map | 78 | | ------------------------------------------- | 79 | | ![Reverse Geocode on Map][reverse_geo_img] | 80 | 81 | [reverse_geo_img]: https://raw.githubusercontent.com/Dhi13man/open_route_service/main/screenshots/reverse_geocoding_map.png 82 | 83 | 6. **[POIs](https://openrouteservice.org/dev/#/api-docs/pois):** 84 | Obtains information about the Points of Interest (POIs) in the area surrounding a geometry which can either be a bounding box, polygon or buffered linestring, or point. 85 | 86 | The Points of Interest can be marked on a map in a Flutter Application, or their properties and information visualized in various ways, or anything else you can think of. 87 | 88 | | Points of Interest Drawn on Map | 89 | | ------------------------------- | 90 | | ![POI Drawn on Map][pois_img] | 91 | 92 | [pois_img]: https://raw.githubusercontent.com/Dhi13man/open_route_service/main/screenshots/pois_map.png 93 | 94 | 7. **[Routing Optimizations](https://openrouteservice.org/dev/#/api-docs/optimization):** 95 | The optimization endpoint solves Vehicle Routing Problems and can be used to schedule multiple vehicles and jobs, respecting time windows, capacities and required skills. 96 | 97 | This service is based on the excellent [Vroom](https://github.com/VROOM-Project/vroom) project. Please also consult [its API documentation](https://github.com/VROOM-Project/vroom/blob/master/docs/API.md). 98 | 99 | | Optimization Data for Vroom Jobs and Vehicles extracted and their route information printed in Console | 100 | | ------------------------------------------------------------------------------------------------------ | 101 | | ![Optimization Route Data][optimization_routes_img] | 102 | 103 | [optimization_routes_img]: https://raw.githubusercontent.com/Dhi13man/open_route_service/main/screenshots/optimization_console.png 104 | 105 | Appropriate tests have also been written for each of the above APIs and can be used to check if the package and/or API are functioning properly. 106 | 107 | ## Getting started 108 | 109 | Run `dart pub add open_route_service` or `flutter pub add open_route_service` in your Dart/Flutter project directory to install the package. 110 | 111 | ## Steps for Usage 112 | 113 | 1. Import the package where needed: 114 | 115 | ```dart 116 | import 'package:open_route_service/open_route_service.dart'; 117 | ``` 118 | 119 | 2. Create a new instance of the class with your [openrouteservice API Key](https://openrouteservice.org/dev/#/signup): 120 | 121 | ```dart 122 | OpenRouteService openrouteservice = OpenRouteService(apiKey: 'YOUR-API-KEY'); 123 | ``` 124 | 125 | 3. Use the handy class methods to easily generate Directions, Isochrones, Time-Distance Matrix, Pelias Geocoding, POIs, Elevation and routing Optimizations etc, letting the package handle all the complex HTTP requests in the background for you. 126 | 127 | ### Example Usage 128 | 129 | To use the package with the [Directions API](https://openrouteservice.org/dev/#/api-docs/v2/directions) to generate and draw a Route on a map in a Flutter application: 130 | 131 | ```dart 132 | import 'package:open_route_service/open_route_service.dart'; 133 | 134 | Future main() async { 135 | // Initialize the openrouteservice with your API key. 136 | final OpenRouteService client = OpenRouteService(apiKey: 'YOUR-API-KEY'); 137 | 138 | // Example coordinates to test between 139 | const double startLat = 37.4220698; 140 | const double startLng = -122.0862784; 141 | const double endLat = 37.4111466; 142 | const double endLng = -122.0792365; 143 | 144 | // Form Route between coordinates 145 | final List routeCoordinates = await client.directionsRouteCoordsGet( 146 | startCoordinate: ORSCoordinate(latitude: startLat, longitude: startLng), 147 | endCoordinate: ORSCoordinate(latitude: endLat, longitude: endLng), 148 | ); 149 | 150 | // Print the route coordinates 151 | routeCoordinates.forEach(print); 152 | 153 | // Map route coordinates to a list of LatLng (requires google_maps_flutter package) 154 | // to be used in the Map route Polyline. 155 | final List routePoints = routeCoordinates 156 | .map((coordinate) => LatLng(coordinate.latitude, coordinate.longitude)) 157 | .toList(); 158 | 159 | // Create Polyline (requires Material UI for Color) 160 | final Polyline routePolyline = Polyline( 161 | polylineId: PolylineId('route'), 162 | visible: true, 163 | points: routePoints, 164 | color: Colors.red, 165 | width: 4, 166 | ); 167 | 168 | // Use Polyline to draw route on map or do anything else with the data :) 169 | } 170 | 171 | ``` 172 | 173 | ## Contribution Guidelines 174 | 175 | - Check the [in-depth Contribution Guide](https://github.com/Dhi13man/open_route_service/blob/main/CONTRIBUTING.md) for exact steps on how to contribute to the package. 176 | 177 | - Contributions are welcome on [GitHub](https://www.github.com/dhi13man/open_route_service). Please ensure all the tests are running before pushing your changes. Write your own tests too! 178 | 179 | - File any [issues or feature requests here,](https://www.github.com/dhi13man/open_route_service/issues) or help me resolve existing ones. :) 180 | 181 | ## Dependencies 182 | 183 | - [Dart](https://www.dartlang.org/) or [Flutter](https://flutter.dev/), for the Dart SDK which this obviously runs on. 184 | - [http,](https://pub.dev/packages/http) for internally making RESTful HTTP Network requests to the API endpoints. 185 | 186 | ## Additional information 187 | 188 | - Please [contribute to openrouteservice API by donating](https://openrouteservice.org/donations/) to help keep the service free and accessible to everyone. 189 | 190 | - Go through the full documentation here: [openrouteservice API Documentation](https://openrouteservice.org/dev/#/api-docs/v2/directions). 191 | 192 | - Reach out to me directly @dhi13man on [Twitter](https://twitter.com/dhi13man) or [GitHub](https://www.github.com/dhi13man) if you have any general questions or suggestions. 193 | 194 | ### Sponsor Message 195 | 196 | The first release of this package was sponsored by [Cashtic](https://cashtic.com/), a Cross-Platform peer-to-peer ATM cash network for Android and Web. Get it on [Google Play!](https://play.google.com/store/apps/details?id=com.cashtic&hl=en&gl=US) 197 | -------------------------------------------------------------------------------- /lib/src/services/directions.dart: -------------------------------------------------------------------------------- 1 | part of 'package:open_route_service/src/open_route_service_base.dart'; 2 | 3 | extension ORSDirections on OpenRouteService { 4 | /// The endpoint of the openrouteservice Directions API. 5 | String get _directionsEndpointURL => '$_baseUrl/v2/directions'; 6 | 7 | /// Fetches the Direction Route information for the route between 8 | /// [startCoordinate] and [endCoordinate] from the openrouteservice API, 9 | /// and returns the entire geojson [GeoJsonFeatureCollection] containing data. 10 | /// 11 | /// To get only the parsed route coordinates, 12 | /// use [ORSDirections.directionsRouteCoordsGet]. 13 | /// 14 | /// Information about the endpoint, parameters, response etc. can be found at: 15 | /// https://openrouteservice.org/dev/#/api-docs/v2/directions/{profile}/get 16 | Future directionsRouteGeoJsonGet({ 17 | required ORSCoordinate startCoordinate, 18 | required ORSCoordinate endCoordinate, 19 | ORSProfile? profileOverride, 20 | }) async { 21 | // If a path parameter override is provided, use it. 22 | final ORSProfile chosenPathParam = profileOverride ?? _defaultProfile; 23 | 24 | // Extract coordinate information. 25 | final double startLat = startCoordinate.latitude; 26 | final double startLng = startCoordinate.longitude; 27 | final double endLat = endCoordinate.latitude; 28 | final double endLng = endCoordinate.longitude; 29 | 30 | // Build the request URL. 31 | final Uri uri = Uri.parse( 32 | '$_directionsEndpointURL/${chosenPathParam.name}?api_key=$_apiKey&start=$startLng,$startLat&end=$endLng,$endLat', 33 | ); 34 | 35 | // Fetch the data. 36 | final Map data = await _openRouteServiceGet(uri: uri); 37 | return GeoJsonFeatureCollection.fromJson(data); 38 | } 39 | 40 | /// Fetches the Direction Route information for the route between 41 | /// [startCoordinate] and [endCoordinate] from the openrouteservice API, and 42 | /// parses it's coordinates to a [List] of [ORSCoordinate] objects. 43 | /// 44 | /// To return the entire [GeoJsonFeatureCollection] containing the response 45 | /// data, use [ORSDirections.directionsRouteGeoJsonGet]. 46 | /// 47 | /// Information about the endpoint and all the parameters can be found at: 48 | /// https://openrouteservice.org/dev/#/api-docs/v2/directions/{profile}/get 49 | Future> directionsRouteCoordsGet({ 50 | required ORSCoordinate startCoordinate, 51 | required ORSCoordinate endCoordinate, 52 | ORSProfile? profileOverride, 53 | }) async { 54 | // Fetch and parse the data. 55 | final GeoJsonFeatureCollection featureCollection = 56 | await directionsRouteGeoJsonGet( 57 | startCoordinate: startCoordinate, 58 | endCoordinate: endCoordinate, 59 | profileOverride: profileOverride, 60 | ); 61 | return featureCollection.features.first.geometry.coordinates.first; 62 | } 63 | 64 | /// Fetches the Direction Route information for the route connecting the 65 | /// various [coordinates] from the openrouteservice API, and returns the 66 | /// entire geojson [GeoJsonFeatureCollection] containing the response data. 67 | /// 68 | /// To get only the parsed route coordinates, 69 | /// use [ORSDirections.directionsMultiRouteCoordsPost]. 70 | /// 71 | /// Information about the endpoint, parameters, response etc. can be found at: 72 | /// https://openrouteservice.org/dev/#/api-docs/v2/directions/{profile}/geojson/post 73 | Future directionsMultiRouteGeoJsonPost({ 74 | required List coordinates, 75 | Object? alternativeRoutes, 76 | List? attributes, 77 | bool continueStraight = false, 78 | bool? elevation, 79 | List? extraInfo, 80 | bool geometrySimplify = false, 81 | String? id, 82 | bool instructions = true, 83 | String instructionsFormat = 'text', 84 | String language = 'en', 85 | bool maneuvers = false, 86 | Object? options, 87 | String preference = 'recommended', 88 | List? radiuses, 89 | bool roundaboutExits = false, 90 | List? skipSegments, 91 | bool suppressWarnings = false, 92 | String units = 'm', 93 | bool geometry = true, 94 | int? maximumSpeed, 95 | ORSProfile? profileOverride, 96 | }) async { 97 | // If a path parameter override is provided, use it. 98 | final ORSProfile chosenPathParam = profileOverride ?? _defaultProfile; 99 | 100 | // Build the request URL. 101 | final Uri uri = 102 | Uri.parse('$_directionsEndpointURL/${chosenPathParam.name}/geojson'); 103 | 104 | // Ready data to be sent. 105 | final Map queryParameters = { 106 | 'coordinates': coordinates 107 | .map>( 108 | (ORSCoordinate coordinate) => 109 | [coordinate.longitude, coordinate.latitude], 110 | ) 111 | .toList(), 112 | 'alternative_routes': alternativeRoutes, 113 | 'attributes': attributes, 114 | 'continue_straight': continueStraight, 115 | 'elevation': elevation, 116 | 'extra_info': extraInfo, 117 | 'geometry_simplify': geometrySimplify, 118 | 'id': id, 119 | 'instructions': instructions, 120 | 'instructions_format': instructionsFormat, 121 | 'language': language, 122 | 'maneuvers': maneuvers, 123 | 'options': options, 124 | 'preference': preference, 125 | 'radiuses': radiuses, 126 | 'roundabout_exits': roundaboutExits, 127 | 'skip_segments': skipSegments, 128 | 'suppress_warnings': suppressWarnings, 129 | 'units': units, 130 | 'geometry': geometry, 131 | 'maximum_speed': maximumSpeed, 132 | }..removeWhere((String _, dynamic value) => value == null); 133 | 134 | // Fetch the data. 135 | final Map data = 136 | await _openRouteServicePost(uri: uri, data: queryParameters); 137 | return GeoJsonFeatureCollection.fromJson(data); 138 | } 139 | 140 | /// Fetches the Direction Route information for the route connecting the 141 | /// various given [coordinates], from the openrouteservice API, and then 142 | /// parses it's coordinates to a [List] of [ORSCoordinate] objects. 143 | /// 144 | /// To return the entire [GeoJsonFeatureCollection] containing the response 145 | /// data, use [ORSDirections.directionsRouteGeoJsonGet]. 146 | /// 147 | /// Information about the endpoint and all the parameters can be found at: 148 | /// https://openrouteservice.org/dev/#/api-docs/v2/directions/{profile}/geojson/post 149 | Future> directionsMultiRouteCoordsPost({ 150 | required List coordinates, 151 | Object? alternativeRoutes, 152 | List? attributes, 153 | bool continueStraight = false, 154 | bool? elevation, 155 | List? extraInfo, 156 | bool geometrySimplify = false, 157 | String? id, 158 | bool instructions = true, 159 | String instructionsFormat = 'text', 160 | String language = 'en', 161 | bool maneuvers = false, 162 | Object? options, 163 | String preference = 'recommended', 164 | List? radiuses, 165 | bool roundaboutExits = false, 166 | List? skipSegments, 167 | bool suppressWarnings = false, 168 | String units = 'm', 169 | bool geometry = true, 170 | int? maximumSpeed, 171 | ORSProfile? profileOverride, 172 | }) async { 173 | // Fetch and parse the data. 174 | final GeoJsonFeatureCollection featureCollection = 175 | await directionsMultiRouteGeoJsonPost( 176 | coordinates: coordinates, 177 | alternativeRoutes: alternativeRoutes, 178 | attributes: attributes, 179 | continueStraight: continueStraight, 180 | elevation: elevation, 181 | extraInfo: extraInfo, 182 | geometrySimplify: geometrySimplify, 183 | id: id, 184 | instructions: instructions, 185 | instructionsFormat: instructionsFormat, 186 | language: language, 187 | maneuvers: maneuvers, 188 | options: options, 189 | preference: preference, 190 | radiuses: radiuses, 191 | roundaboutExits: roundaboutExits, 192 | skipSegments: skipSegments, 193 | suppressWarnings: suppressWarnings, 194 | units: units, 195 | geometry: geometry, 196 | maximumSpeed: maximumSpeed, 197 | profileOverride: profileOverride, 198 | ); 199 | return featureCollection.features.first.geometry.coordinates.first; 200 | } 201 | 202 | /// Fetches the Direction Route information for the route connecting the 203 | /// various [coordinates] from the openrouteservice API, and returns the 204 | /// entire geojson [DirectionRouteData] containing the response data. 205 | /// 206 | /// To get the geojson [GeoJsonFeatureCollection] containing the response 207 | /// data, use [ORSDirections.directionsMultiRouteGeoJsonPost]. 208 | /// 209 | /// To get only the parsed route coordinates, 210 | /// use [ORSDirections.directionsMultiRouteCoordsPost]. 211 | /// 212 | /// Information about the endpoint, parameters, response etc. can be found at: 213 | /// https://openrouteservice.org/dev/#/api-docs/v2/directions/{profile}/post 214 | Future> directionsMultiRouteDataPost({ 215 | required List coordinates, 216 | Object? alternativeRoutes, 217 | List? attributes, 218 | bool continueStraight = false, 219 | bool? elevation, 220 | List? extraInfo, 221 | bool geometrySimplify = false, 222 | String? id, 223 | bool instructions = true, 224 | String instructionsFormat = 'text', 225 | String language = 'en', 226 | bool maneuvers = false, 227 | Object? options, 228 | String preference = 'recommended', 229 | List? radiuses, 230 | bool roundaboutExits = false, 231 | List? skipSegments, 232 | bool suppressWarnings = false, 233 | String units = 'm', 234 | bool geometry = true, 235 | int? maximumSpeed, 236 | ORSProfile? profileOverride, 237 | }) async { 238 | // If a path parameter override is provided, use it. 239 | final ORSProfile chosenPathParam = profileOverride ?? _defaultProfile; 240 | 241 | // Build the request URL. 242 | final Uri uri = 243 | Uri.parse('$_directionsEndpointURL/${chosenPathParam.name}'); 244 | 245 | // Ready data to be sent. 246 | final Map queryParameters = { 247 | 'coordinates': coordinates 248 | .map>( 249 | (ORSCoordinate coordinate) => 250 | [coordinate.longitude, coordinate.latitude], 251 | ) 252 | .toList(), 253 | 'alternative_routes': alternativeRoutes, 254 | 'attributes': attributes, 255 | 'continue_straight': continueStraight, 256 | 'elevation': elevation, 257 | 'extra_info': extraInfo, 258 | 'geometry_simplify': geometrySimplify, 259 | 'id': id, 260 | 'instructions': instructions, 261 | 'instructions_format': instructionsFormat, 262 | 'language': language, 263 | 'maneuvers': maneuvers, 264 | 'options': options, 265 | 'preference': preference, 266 | 'radiuses': radiuses, 267 | 'roundabout_exits': roundaboutExits, 268 | 'skip_segments': skipSegments, 269 | 'suppress_warnings': suppressWarnings, 270 | 'units': units, 271 | 'geometry': geometry, 272 | 'maximum_speed': maximumSpeed, 273 | }..removeWhere((String _, dynamic value) => value == null); 274 | 275 | // Fetch the data. 276 | final Map data = 277 | await _openRouteServicePost(uri: uri, data: queryParameters); 278 | return (data['routes'] as List) 279 | .map( 280 | (dynamic route) => DirectionRouteData.fromJson(route), 281 | ) 282 | .toList(); 283 | } 284 | } 285 | -------------------------------------------------------------------------------- /lib/src/models/geojson_feature_models.dart: -------------------------------------------------------------------------------- 1 | import 'package:open_route_service/src/models/coordinate_model.dart'; 2 | 3 | /// Data that represents GeoJSON feature collection for Isochrone or Direction 4 | /// Data models in their respective endpoints. 5 | /// 6 | /// Includes its bounding box coordinates [bbox] and [features]. 7 | /// 8 | /// Used by APIs: 9 | /// 10 | /// https://openrouteservice.org/dev/#/api-docs/geocode/search/get 11 | /// 12 | /// https://openrouteservice.org/dev/#/api-docs/geocode/autocomplete/get 13 | /// 14 | /// https://openrouteservice.org/dev/#/api-docs/v2/directions/{profile}/geojson/post 15 | /// 16 | /// https://openrouteservice.org/dev/#/api-docs/v2/isochrones/{profile}/post 17 | class GeoJsonFeatureCollection { 18 | const GeoJsonFeatureCollection({required this.bbox, required this.features}); 19 | 20 | /// Generate a [GeoJsonFeatureCollection] from a received [Map] having keys 21 | /// 'bbox' and 'features'. 22 | factory GeoJsonFeatureCollection.fromJson(Map json) => 23 | GeoJsonFeatureCollection( 24 | bbox: ((json['bbox'] ?? []) as List).length < 4 25 | ? [] 26 | : [ 27 | ORSCoordinate( 28 | longitude: json['bbox'][0], 29 | latitude: json['bbox'][1], 30 | ), 31 | ORSCoordinate( 32 | longitude: json['bbox'][2], 33 | latitude: json['bbox'][3], 34 | ), 35 | ], 36 | features: (json['features'] as List) 37 | .map( 38 | (dynamic e) => GeoJsonFeature.fromJson(e as Map), 39 | ) 40 | .toList(), 41 | ); 42 | 43 | /// The bounding box of the requested feature collection's area. 44 | /// Should have 2 coordinates. 45 | final List bbox; 46 | 47 | /// The list of features of the requested feature collection. 48 | final List features; 49 | 50 | /// Converts the [GeoJsonFeatureCollection] to a [Map] with keys 'type', 51 | /// 'bbox' and 'features'. 52 | /// 53 | /// The 'bbox' key is converted to list of 4 [double]s implying 2 coordinates. 54 | Map toJson() => { 55 | 'type': 'FeatureCollection', 56 | 'bbox': bbox.length == 2 57 | ? [ 58 | bbox[0].longitude, 59 | bbox[0].latitude, 60 | bbox[1].longitude, 61 | bbox[1].latitude, 62 | ] 63 | : [], 64 | 'features': features 65 | .map>( 66 | (GeoJsonFeature feature) => feature.toJson(), 67 | ) 68 | .toList(), 69 | }; 70 | 71 | @override 72 | String toString() => toJson().toString(); 73 | } 74 | 75 | /// A feature of an Isochrone or Directions API endpoint response formatted as 76 | /// geojson. 77 | /// 78 | /// Includes its [geometry] and [properties]. 79 | class GeoJsonFeature { 80 | const GeoJsonFeature({ 81 | required this.type, 82 | required this.properties, 83 | required this.geometry, 84 | this.bbox, 85 | }); 86 | 87 | GeoJsonFeature.fromJson(Map json) 88 | : type = json['type'], 89 | properties = json['properties'], 90 | geometry = GeoJsonFeatureGeometry.fromJson(json['geometry']), 91 | bbox = json['bbox'] == null 92 | ? null 93 | : [ 94 | ORSCoordinate( 95 | longitude: json['bbox'][0], 96 | latitude: json['bbox'][1], 97 | ), 98 | ORSCoordinate( 99 | longitude: json['bbox'][2], 100 | latitude: json['bbox'][3], 101 | ), 102 | ]; 103 | 104 | /// The type of the feature. 105 | final String type; 106 | 107 | /// The properties of the feature as [Map] of [String] keys and [dynamic] 108 | /// values to keep up with the API's unconstrained response. 109 | /// 110 | /// Possible Data Models include responses of: 111 | /// 112 | /// https://openrouteservice.org/dev/#/api-docs/geocode/search/get 113 | /// 114 | /// https://openrouteservice.org/dev/#/api-docs/geocode/autocomplete/get 115 | /// 116 | /// https://openrouteservice.org/dev/#/api-docs/v2/isochrones/{profile}/post 117 | /// 118 | /// https://openrouteservice.org/dev/#/api-docs/v2/directions/{profile}/geojson/post 119 | final Map properties; 120 | 121 | /// The geometry of the feature as [GeoJsonFeatureGeometry]. 122 | final GeoJsonFeatureGeometry geometry; 123 | 124 | /// The bounding box of the requested feature's area. 125 | /// 126 | /// Should have 2 coordinates. 127 | final List? bbox; 128 | 129 | /// Converts the [GeoJsonFeature] to a [Map] with keys 'type', 'properties' 130 | /// and 'geometry'. 131 | Map toJson() => { 132 | 'type': type, 133 | 'properties': properties, 134 | 'geometry': geometry.toJson(), 135 | if (bbox != null) 136 | 'bbox': [ 137 | bbox![0].longitude, 138 | bbox![0].latitude, 139 | bbox![1].longitude, 140 | bbox![1].latitude, 141 | ], 142 | }; 143 | 144 | @override 145 | String toString() => toJson().toString(); 146 | } 147 | 148 | /// The geometry of a [GeoJsonFeature]. 149 | /// 150 | /// Includes its [type] and [List] of [List] of [ORSCoordinate], [coordinates]. 151 | class GeoJsonFeatureGeometry { 152 | const GeoJsonFeatureGeometry({ 153 | required this.type, 154 | required this.coordinates, 155 | required this.internalType, 156 | }); 157 | 158 | factory GeoJsonFeatureGeometry.fromJson(Map json) { 159 | final dynamic type = json['type']; 160 | final dynamic coordinates = json['coordinates']; 161 | final bool isNotListOrIsEmptyList = 162 | coordinates is! List || coordinates.isEmpty; 163 | if (isNotListOrIsEmptyList) { 164 | return _generateEmptyGeometry(type); 165 | } 166 | 167 | final List dynamicList = coordinates; 168 | final bool isFirstElementList = 169 | dynamicList.isNotEmpty && dynamicList.first is List; 170 | if (isFirstElementList) { 171 | final List> dynamicListList = dynamicList 172 | .map>((dynamic c) => c as List) 173 | .toList(); 174 | // For Isochrone feature geometry, it has a list of list of coordinates. 175 | final bool isFirstFirstElementList = dynamicListList.first.isNotEmpty && 176 | dynamicListList.first.first is List; 177 | if (isFirstFirstElementList) { 178 | return _generateIsochroneGeometry(type, dynamicListList); 179 | } 180 | 181 | // For direction feature geometry, it has a list of coordinates. 182 | final bool isFirstFirstElementNum = dynamicListList.first.isNotEmpty && 183 | dynamicListList.first.first is num; 184 | if (isFirstFirstElementNum) { 185 | return _generateDirectionGeometry(type, dynamicListList); 186 | } 187 | } 188 | 189 | // For Point feature geometry, it has a single coordinate. 190 | return _generatePointGeometry(type, coordinates); 191 | } 192 | 193 | final GsonFeatureGeometryCoordinatesType internalType; 194 | 195 | /// Coordinates associated with the feature geometry. 196 | /// 197 | /// It might either be a proper [List] of [List] of [ORSCoordinate] or a 198 | /// [List] of [ORSCoordinate] wrapped in an empty [List], or a [ORSCoordinate] 199 | /// wrapped in an empty [List] which is again wrapped in an empty [List]. 200 | /// 201 | /// Depends upon which endpoint [GeoJsonFeatureGeometry] was extracted from. 202 | final List> coordinates; 203 | 204 | /// The type of the feature geometry. 205 | final String type; 206 | 207 | /// Converts the [GeoJsonFeatureGeometry] to a [Map] with keys 'type' and 208 | /// 'coordinates'. 209 | /// 210 | /// The [coordinates] are converted to a [List] of [List]s of 211 | /// [List]s of 2 elements. 212 | Map toJson() { 213 | switch (internalType) { 214 | case GsonFeatureGeometryCoordinatesType.listList: 215 | return { 216 | 'type': type, 217 | 'coordinates': coordinates 218 | .map>>( 219 | (List c) => c 220 | .map>( 221 | (ORSCoordinate c) => c.toList(), 222 | ) 223 | .toList(), 224 | ) 225 | .toList(), 226 | }; 227 | case GsonFeatureGeometryCoordinatesType.list: 228 | return { 229 | 'type': type, 230 | 'coordinates': coordinates 231 | .map>( 232 | (List c) => c.first.toList(), 233 | ) 234 | .toList(), 235 | }; 236 | case GsonFeatureGeometryCoordinatesType.single: 237 | return { 238 | 'type': type, 239 | 'coordinates': coordinates.first.first.toList(), 240 | }; 241 | 242 | case GsonFeatureGeometryCoordinatesType.empty: 243 | return { 244 | 'type': type, 245 | 'coordinates': >[], 246 | }; 247 | } 248 | } 249 | 250 | @override 251 | String toString() => toJson().toString(); 252 | 253 | /// For Isochrone feature geometry, it has a list of list of coordinates. 254 | static GeoJsonFeatureGeometry _generateIsochroneGeometry( 255 | String type, 256 | List> dynamicListList, 257 | ) { 258 | final List> coordinateListList = dynamicListList 259 | .map>>( 260 | (List c) => 261 | c.map>((dynamic c) => c as List).toList(), 262 | ) 263 | .map>>( 264 | (List> c) => c 265 | .map>( 266 | (List c) => 267 | c.map((dynamic c) => c as num).toList(), 268 | ) 269 | .toList(), 270 | ) 271 | .map>( 272 | (List> c) => c 273 | .map((List c) => ORSCoordinate.fromList(c)) 274 | .toList(), 275 | ) 276 | .toList(); 277 | return GeoJsonFeatureGeometry( 278 | type: type, 279 | coordinates: coordinateListList, 280 | internalType: GsonFeatureGeometryCoordinatesType.listList, 281 | ); 282 | } 283 | 284 | /// For direction feature geometry, it has a list of coordinates. 285 | static _generateDirectionGeometry( 286 | String type, 287 | List> dynamicListList, 288 | ) { 289 | final List coordinateList = dynamicListList 290 | .map( 291 | (List c) => ORSCoordinate.fromList( 292 | c.map((dynamic c) => (c as num).toDouble()).toList(), 293 | ), 294 | ) 295 | .toList(); 296 | return GeoJsonFeatureGeometry( 297 | type: type, 298 | coordinates: >[coordinateList], 299 | internalType: GsonFeatureGeometryCoordinatesType.list, 300 | ); 301 | } 302 | 303 | /// For Point feature geometry, it has a single coordinate. 304 | static _generatePointGeometry(String type, dynamic coordinates) { 305 | final ORSCoordinate coordinate = ORSCoordinate.fromList(coordinates); 306 | return GeoJsonFeatureGeometry( 307 | type: type, 308 | coordinates: >[ 309 | [coordinate], 310 | ], 311 | internalType: GsonFeatureGeometryCoordinatesType.single, 312 | ); 313 | } 314 | 315 | /// For Point feature geometry, it has a single coordinate. 316 | static _generateEmptyGeometry(String type) { 317 | return GeoJsonFeatureGeometry( 318 | type: type, 319 | coordinates: >[[]], 320 | internalType: GsonFeatureGeometryCoordinatesType.empty, 321 | ); 322 | } 323 | } 324 | 325 | enum GsonFeatureGeometryCoordinatesType { listList, list, single, empty } 326 | -------------------------------------------------------------------------------- /lib/src/models/optimization_models/optimization_data_models.dart: -------------------------------------------------------------------------------- 1 | import 'package:open_route_service/src/models/coordinate_model.dart'; 2 | import 'package:open_route_service/src/models/optimization_models/vroom_data_models.dart' 3 | show VroomVehicleStepType; 4 | 5 | /// A class encapsulating the Optimization Data received from the Optimization 6 | /// endpoint of the openrouteservice API. 7 | /// 8 | /// Includes the optimizations data's [code], its [summary], [unassigned] 9 | /// and [routes]. 10 | /// 11 | /// https://openrouteservice.org/dev/#/api-docs/optimization 12 | /// https://github.com/VROOM-Project/vroom 13 | class OptimizationData { 14 | const OptimizationData({ 15 | required this.code, 16 | required this.summary, 17 | required this.routes, 18 | this.unassigned = const [], 19 | }); 20 | 21 | /// Generates a [OptimizationData] from a [Map] received from the API having 22 | /// the keys 'code', 'summary', 'routes' and 'unassigned'. 23 | factory OptimizationData.fromJson(Map json) => 24 | OptimizationData( 25 | code: json['code'], 26 | summary: OptimizationSummary.fromJson(json['summary']), 27 | unassigned: json['unassigned'] as List, 28 | routes: (json['routes'] as List) 29 | .map( 30 | (dynamic e) => OptimizationRoute.fromJson(e), 31 | ) 32 | .toList(), 33 | ); 34 | 35 | /// [int] code of the optimization data. 36 | final int code; 37 | 38 | /// Summary of the optimization data. 39 | final OptimizationSummary summary; 40 | 41 | /// [List] of Unassigned data. 42 | final List unassigned; 43 | 44 | /// [List] of [OptimizationRoute]s of the optimization data. 45 | final List routes; 46 | 47 | /// Returns a [Map] representation of the [OptimizationData] object. 48 | /// 49 | /// The keys of the [Map] are 'code', 'summary', 'routes' and 'unassigned'. 50 | Map toJson() => { 51 | 'code': code, 52 | 'summary': summary.toJson(), 53 | 'routes': routes 54 | .map>((OptimizationRoute e) => e.toJson()) 55 | .toList(), 56 | 'unassigned': unassigned, 57 | }; 58 | 59 | @override 60 | String toString() => toJson().toString(); 61 | 62 | @override 63 | bool operator ==(Object other) => 64 | other is OptimizationData && 65 | runtimeType == other.runtimeType && 66 | code == other.code && 67 | summary == other.summary && 68 | unassigned == other.unassigned && 69 | routes == other.routes; 70 | 71 | @override 72 | int get hashCode => code.hashCode ^ summary.hashCode ^ unassigned.hashCode; 73 | } 74 | 75 | /// A class encapsulating the data included in the Optimization's Summary. 76 | /// 77 | /// Includes the optimization's [cost], [unassigned], [delivery], [pickup], 78 | /// [service], [duration], [waitingTime] and its [computingTimes] map. 79 | class OptimizationSummary { 80 | const OptimizationSummary({ 81 | this.cost, 82 | this.unassigned, 83 | this.amount, 84 | this.delivery, 85 | this.pickup, 86 | this.service = 0, 87 | this.duration, 88 | this.waitingTime, 89 | this.computingTimes, 90 | }); 91 | 92 | /// Generates [OptimizationSummary] from a [Map] received from the API having 93 | /// the keys 'cost', 'unassigned', 'delivery', 'amount', 'pickup', 'service', 94 | /// 'duration', 'waiting_time' and 'computing_times'. 95 | factory OptimizationSummary.fromJson(Map json) => 96 | OptimizationSummary( 97 | cost: json['cost'], 98 | unassigned: json['unassigned'], 99 | amount: json['amount'] == null 100 | ? null 101 | : (json['amount'] as List) 102 | .map((dynamic e) => e as int) 103 | .toList(), 104 | delivery: json['delivery'] == null 105 | ? null 106 | : (json['delivery'] as List) 107 | .map((dynamic e) => e as int) 108 | .toList(), 109 | pickup: json['pickup'] == null 110 | ? null 111 | : (json['pickup'] as List) 112 | .map((dynamic e) => e as int) 113 | .toList(), 114 | service: json['service'], 115 | duration: json['duration'], 116 | waitingTime: json['waiting_time'], 117 | computingTimes: json['computing_times'] as Map, 118 | ); 119 | 120 | /// [int] Optimized cost. 121 | final int? cost; 122 | 123 | /// [int] Count of unassigned values. 124 | final int? unassigned; 125 | 126 | /// [List] of [int] describing multidimensional quantities of this 127 | /// Optimization for amount. 128 | final List? amount; 129 | 130 | /// [List] of [int] describing multidimensional quantities of this 131 | /// Optimization for delivery. 132 | final List? delivery; 133 | 134 | /// [List] of [int] describing multidimensional quantities of this 135 | /// Optimization for pickup. 136 | final List? pickup; 137 | 138 | /// [int] describing the service duration of this job. Defaults to 0. 139 | final int? service; 140 | 141 | /// [int] describing the duration of this Optimized job. 142 | final int? duration; 143 | 144 | /// [int] describing the waiting time for this Optimized job. 145 | final int? waitingTime; 146 | 147 | /// [Map] of [int] describing the computing times for each task. 148 | final Map? computingTimes; 149 | 150 | /// Returns a [Map] representation of the [OptimizationSummary] object. 151 | /// The keys of the [Map] are 'cost', 'unassigned', 'amount', 'delivery', 152 | /// 'pickup', 'service', 'duration', 'waiting_time' and 'computing_times'. 153 | Map toJson() => { 154 | 'cost': cost, 155 | 'unassigned': unassigned, 156 | 'amount': amount, 157 | 'delivery': delivery, 158 | 'pickup': pickup, 159 | 'service': service, 160 | 'duration': duration, 161 | 'waiting_time': waitingTime, 162 | 'computing_times': computingTimes, 163 | }..removeWhere((String _, dynamic value) => value == null); 164 | 165 | @override 166 | String toString() => toJson().toString(); 167 | 168 | @override 169 | bool operator ==(Object other) => 170 | other is OptimizationSummary && toString() == other.toString(); 171 | 172 | @override 173 | int get hashCode => toJson().hashCode; 174 | } 175 | 176 | /// A class encapsulating the data included in the Optimization's Route. 177 | /// 178 | /// Includes the optimization route's [vehicle], [cost], [delivery], [amount], 179 | /// [pickup], [service], [duration], [waitingTime] and its [steps] list. 180 | class OptimizationRoute { 181 | const OptimizationRoute({ 182 | this.vehicle, 183 | this.cost, 184 | this.delivery, 185 | this.amount, 186 | this.pickup, 187 | this.service = 0, 188 | this.duration, 189 | this.waitingTime, 190 | this.steps = const [], 191 | }); 192 | 193 | /// Generates [OptimizationRoute] from a [Map] received from the API having 194 | /// the keys 'vehicle', 'cost', 'delivery', 'amount', 'pickup', 'service', 195 | /// 'duration', 'waiting_time' and 'steps'. 196 | factory OptimizationRoute.fromJson(Map json) => 197 | OptimizationRoute( 198 | vehicle: json['vehicle'], 199 | cost: json['cost'], 200 | amount: json['amount'] == null 201 | ? null 202 | : (json['amount'] as List) 203 | .map((dynamic e) => e as int) 204 | .toList(), 205 | delivery: json['delivery'] == null 206 | ? null 207 | : (json['delivery'] as List) 208 | .map((dynamic e) => e as int) 209 | .toList(), 210 | pickup: json['pickup'] == null 211 | ? null 212 | : (json['pickup'] as List) 213 | .map((dynamic e) => e as int) 214 | .toList(), 215 | service: json['service'], 216 | duration: json['duration'], 217 | waitingTime: json['waiting_time'], 218 | steps: json['steps'] == null 219 | ? null 220 | : (json['steps'] as List) 221 | .map( 222 | (dynamic e) => OptimizationRouteStep.fromJson(e), 223 | ) 224 | .toList(), 225 | ); 226 | 227 | /// [int] describing the vehicle number used for this route. 228 | final int? vehicle; 229 | 230 | /// [int] describing the cost of this route. 231 | final int? cost; 232 | 233 | /// [List] of [int] describing multidimensional quantities of this 234 | /// [OptimizationRoute] for delivery. 235 | final List? delivery; 236 | 237 | /// [List] of [int] describing multidimensional quantities of this 238 | /// [OptimizationRoute] for amount. 239 | final List? amount; 240 | 241 | /// [List] of [int] describing multidimensional quantities of this 242 | /// [OptimizationRoute] for pickup. 243 | final List? pickup; 244 | 245 | /// [int] describing the service duration of this job. Defaults to 0. 246 | final int? service; 247 | 248 | /// [int] describing the duration of this Optimized Route. 249 | final int? duration; 250 | 251 | /// [int] describing the waiting time for this Optimized Route. 252 | final int? waitingTime; 253 | 254 | /// [List] of [OptimizationRouteStep]s of this [OptimizationRoute]. 255 | final List? steps; 256 | 257 | /// Returns a [Map] representation of the [OptimizationRoute] object. 258 | /// The keys of the [Map] are 'vehicle', 'cost', 'delivery', 'amount', 259 | /// 'pickup', 'service', 'duration', 'waiting_time' and 'steps'. 260 | Map toJson() => { 261 | 'vehicle': vehicle, 262 | 'cost': cost, 263 | 'amount': amount, 264 | 'delivery': delivery, 265 | 'pickup': pickup, 266 | 'service': service, 267 | 'duration': duration, 268 | 'waiting_time': waitingTime, 269 | 'steps': steps 270 | ?.map>((OptimizationRouteStep e) => e.toJson()) 271 | .toList(), 272 | }..removeWhere((String _, dynamic value) => value == null); 273 | 274 | @override 275 | String toString() => toJson().toString(); 276 | 277 | @override 278 | bool operator ==(Object other) => 279 | other is OptimizationRoute && toString() == other.toString(); 280 | 281 | @override 282 | int get hashCode => toJson().hashCode; 283 | } 284 | 285 | /// The class encapsulating the data included in a Optimization Route's Step. 286 | /// 287 | /// Includes the Optimization Route Step's [type], [location], [arrival], 288 | /// [duration], [id], [service], [waitingTime], [job] and [load]. 289 | class OptimizationRouteStep { 290 | const OptimizationRouteStep({ 291 | required this.type, 292 | required this.location, 293 | required this.arrival, 294 | required this.duration, 295 | this.id, 296 | this.service = 0, 297 | this.waitingTime, 298 | this.job, 299 | this.load = const [], 300 | }); 301 | 302 | /// Generates [OptimizationRouteStep] from a [Map] received from API having 303 | /// the keys 'type', 'location', 'arrival', 'duration', 'id', 'service', 304 | /// 'waiting_time', 'job' and 'load'. 305 | factory OptimizationRouteStep.fromJson(Map json) => 306 | OptimizationRouteStep( 307 | type: json['type'], 308 | location: ORSCoordinate.fromList(json['location'] as List), 309 | arrival: json['arrival'], 310 | duration: json['duration'], 311 | id: json['id'], 312 | service: json['service'], 313 | waitingTime: json['waiting_time'], 314 | job: json['job'], 315 | load: json['load'] == null 316 | ? null 317 | : (json['load'] as List) 318 | .map((dynamic e) => e as int) 319 | .toList(), 320 | ); 321 | 322 | /// Corresponds to possible [VroomVehicleStepType] values: 323 | /// 'start', 'job', 'pickup', 'delivery', 'break' and 'end' respectively. 324 | final String type; 325 | 326 | /// [ORSCoordinate] of the Route Step's location. 327 | final ORSCoordinate location; 328 | 329 | /// [int] describing the identifier of this Route Step. 330 | final int? id; 331 | 332 | /// [int] describing the service duration of this job. Defaults to 0. 333 | final int? service; 334 | 335 | /// [int] describing the waiting time for this Optimized Route Step. 336 | final int? waitingTime; 337 | 338 | /// [int] describing the job number of this Optimized Route Step. 339 | final int? job; 340 | 341 | /// [List] of [int] describing multidimensional quantities of this 342 | /// [OptimizationRouteStep]'s load. 343 | final List? load; 344 | 345 | /// [int] describing the arrival time of this Optimized Route Step. 346 | final int arrival; 347 | 348 | /// [int] describing the duration of this Optimized Route Step. 349 | final int duration; 350 | 351 | /// Returns a [Map] representation of the [OptimizationRouteStep] object. 352 | /// The keys of the [Map] are 'type', 'location', 'arrival', 'duration', 353 | /// 'id', 'service', 'waiting_time', 'job' and 'load'. 354 | Map toJson() => { 355 | 'type': type, 356 | 'location': location.toList(), 357 | 'arrival': arrival, 358 | 'duration': duration, 359 | 'id': id, 360 | 'service': service, 361 | 'waiting_time': waitingTime, 362 | 'job': job, 363 | 'load': load, 364 | }..removeWhere((String _, dynamic value) => value == null); 365 | 366 | @override 367 | String toString() => toJson().toString(); 368 | 369 | @override 370 | bool operator ==(Object other) => 371 | other is OptimizationRouteStep && toString() == other.toString(); 372 | 373 | @override 374 | int get hashCode => toJson().hashCode; 375 | } 376 | -------------------------------------------------------------------------------- /lib/src/services/geocode.dart: -------------------------------------------------------------------------------- 1 | part of 'package:open_route_service/src/open_route_service_base.dart'; 2 | 3 | extension ORSGeocode on OpenRouteService { 4 | /// The endpoint of the openrouteservice Geocode API. 5 | String get _geocodeEndpointURL => '$_baseUrl/geocode'; 6 | 7 | /// Available Sources for Geocoding 8 | Set get geocodeSourcesAvailable => 9 | {'openstreetmap', 'openaddresses', 'whosonfirst', 'geonames'}; 10 | 11 | /// Available Layer settings for Geocoding 12 | Set get geocodeLayersAvailable => const { 13 | 'address', 14 | 'venue', 15 | 'neighbourhood', 16 | 'locality', 17 | 'borough', 18 | 'localadmin', 19 | 'county', 20 | 'macrocounty', 21 | 'region', 22 | 'macroregion', 23 | 'country', 24 | 'coarse', 25 | }; 26 | 27 | /// Fetches the Geocode Autocomplete data for the given [text] from chosen 28 | /// [sources] and using settings [layers], and returns the entire geojson 29 | /// [GeoJsonFeatureCollection] containing the data. 30 | /// 31 | /// Can be constrained using the various [ORSCoordinate] parameters. 32 | /// 33 | /// [GeoJsonFeatureCollection] -> [GeoJsonFeatureCollection.features] 34 | /// is a list of [GeoJsonFeature]s whose [GeoJsonFeature.properties] have all 35 | /// the information about the result of the autocomplete query. 36 | /// 37 | /// Information about the endpoint, parameters, response etc. can be found at: 38 | /// 39 | /// https://openrouteservice.org/dev/#/api-docs/geocode/autocomplete/get 40 | /// 41 | /// https://github.com/pelias/documentation/blob/master/autocomplete.md 42 | Future geocodeAutoCompleteGet({ 43 | required String text, 44 | ORSCoordinate? focusPointCoordinate, 45 | ORSCoordinate? boundaryRectangleMinCoordinate, 46 | ORSCoordinate? boundaryRectangleMaxCoordinate, 47 | String? boundaryCountry, 48 | List? sources, 49 | List layers = const [], 50 | }) async { 51 | // Validate input. 52 | _bothOrNeitherNullValidation( 53 | boundaryRectangleMinCoordinate, 54 | boundaryRectangleMaxCoordinate, 55 | ); 56 | // Form input from parameters. 57 | sources ??= geocodeSourcesAvailable.toList(); 58 | final String sourcesString = _validateAndJoinList( 59 | inputArr: sources, 60 | availableIterable: geocodeSourcesAvailable, 61 | ); 62 | final String layersString = _validateAndJoinList( 63 | inputArr: layers, 64 | availableIterable: geocodeLayersAvailable, 65 | ); 66 | String getURIString = 67 | '$_geocodeEndpointURL/autocomplete?api_key=$_apiKey&text=$text&sources=$sourcesString'; 68 | if (layersString.isNotEmpty) { 69 | getURIString += '&layers=$layersString'; 70 | } 71 | if (focusPointCoordinate != null) { 72 | getURIString += '&focus.point.lon=${focusPointCoordinate.longitude}'; 73 | getURIString += '&focus.point.lat=${focusPointCoordinate.latitude}'; 74 | } 75 | if (boundaryRectangleMinCoordinate != null) { 76 | getURIString += 77 | '&boundary.rect.min_lon=${boundaryRectangleMinCoordinate.longitude}'; 78 | getURIString += 79 | '&boundary.rect.min_lat=${boundaryRectangleMinCoordinate.latitude}'; 80 | } 81 | if (boundaryRectangleMaxCoordinate != null) { 82 | getURIString += 83 | '&boundary.rect.max_lon=${boundaryRectangleMaxCoordinate.longitude}'; 84 | getURIString += 85 | '&boundary.rect.max_lat=${boundaryRectangleMaxCoordinate.latitude}'; 86 | } 87 | if (boundaryCountry != null) { 88 | getURIString += '&boundary.country=$boundaryCountry'; 89 | } 90 | 91 | // Build the request URL. 92 | final Uri uri = Uri.parse(getURIString); 93 | final Map data = await _openRouteServiceGet(uri: uri); 94 | return GeoJsonFeatureCollection.fromJson(data); 95 | } 96 | 97 | /// Fetches the Geocode Search data for the given search [text] from chosen 98 | /// [sources] and using settings [layers], and returns the entire geojson 99 | /// [GeoJsonFeatureCollection] containing the data. 100 | /// 101 | /// Can be constrained using the various [ORSCoordinate] parameters. 102 | /// 103 | /// [GeoJsonFeatureCollection] -> [GeoJsonFeatureCollection.features] 104 | /// is a list of [GeoJsonFeature]s whose [GeoJsonFeature.properties] have all 105 | /// the information about the result of the search. 106 | /// 107 | /// Information about the endpoint, parameters, response etc. can be found at: 108 | /// 109 | /// https://openrouteservice.org/dev/#/api-docs/geocode/search/get 110 | /// 111 | /// https://github.com/pelias/documentation/blob/master/search.md#search-the-world 112 | Future geocodeSearchGet({ 113 | required String text, 114 | ORSCoordinate? focusPointCoordinate, 115 | ORSCoordinate? boundaryRectangleMinCoordinate, 116 | ORSCoordinate? boundaryRectangleMaxCoordinate, 117 | ORSCoordinate? boundaryCircleCoordinate, 118 | double boundaryCircleRadius = 50, 119 | String? boundaryGid, 120 | String? boundaryCountry, 121 | List? sources, 122 | List layers = const [], 123 | int size = 10, 124 | }) async { 125 | // Validate Input. 126 | _bothOrNeitherNullValidation( 127 | boundaryRectangleMinCoordinate, 128 | boundaryRectangleMaxCoordinate, 129 | ); 130 | // Form input from parameters. 131 | sources ??= geocodeSourcesAvailable.toList(); 132 | final String sourcesString = _validateAndJoinList( 133 | inputArr: sources, 134 | availableIterable: geocodeSourcesAvailable, 135 | ); 136 | final String layersString = _validateAndJoinList( 137 | inputArr: layers, 138 | availableIterable: geocodeLayersAvailable, 139 | ); 140 | String getURIString = 141 | '$_geocodeEndpointURL/search?api_key=$_apiKey&text=$text&sources=$sourcesString&size=$size'; 142 | if (layersString.isNotEmpty) { 143 | getURIString += '&layers=$layersString'; 144 | } 145 | if (focusPointCoordinate != null) { 146 | getURIString += '&focus.point.lon=${focusPointCoordinate.longitude}'; 147 | getURIString += '&focus.point.lat=${focusPointCoordinate.latitude}'; 148 | } 149 | if (boundaryRectangleMinCoordinate != null) { 150 | getURIString += 151 | '&boundary.rect.min_lon=${boundaryRectangleMinCoordinate.longitude}'; 152 | getURIString += 153 | '&boundary.rect.min_lat=${boundaryRectangleMinCoordinate.latitude}'; 154 | } 155 | if (boundaryRectangleMaxCoordinate != null) { 156 | getURIString += 157 | '&boundary.rect.max_lon=${boundaryRectangleMaxCoordinate.longitude}'; 158 | getURIString += 159 | '&boundary.rect.max_lat=${boundaryRectangleMaxCoordinate.latitude}'; 160 | } 161 | if (boundaryCircleCoordinate != null) { 162 | getURIString += 163 | '&boundary.circle.lon=${boundaryCircleCoordinate.longitude}'; 164 | getURIString += 165 | '&boundary.circle.lat=${boundaryCircleCoordinate.latitude}'; 166 | getURIString += '&boundary.circle.radius=$boundaryCircleRadius'; 167 | } 168 | if (boundaryGid != null) { 169 | getURIString += '&boundary.gid=$boundaryGid'; 170 | } 171 | if (boundaryCountry != null) { 172 | getURIString += '&boundary.country=$boundaryCountry'; 173 | } 174 | 175 | // Build the request URL. 176 | final Uri uri = Uri.parse(getURIString); 177 | final Map data = await _openRouteServiceGet(uri: uri); 178 | return GeoJsonFeatureCollection.fromJson(data); 179 | } 180 | 181 | /// Fetches the Geocode Structured Search data for the given search [address] 182 | /// and/or [neighbourhood] and/or [country] and/or [postalcode] and/or 183 | /// [region] and/or [county] and/or [locality] and/or [borough] from chosen 184 | /// [sources] and using settings [layers], and returns the entire geojson 185 | /// [GeoJsonFeatureCollection] containing the data. Uses the Structured Search 186 | /// API endpoint. 187 | /// 188 | /// Can be constrained using the various [ORSCoordinate] parameters. 189 | /// 190 | /// [GeoJsonFeatureCollection] -> [GeoJsonFeatureCollection.features] 191 | /// is a list of [GeoJsonFeature]s whose [GeoJsonFeature.properties] have all 192 | /// the information about the result of the search. 193 | /// 194 | /// At least one of the following fields is required: venue, address, 195 | /// neighbourhood, borough, locality, county, region, postalcode, country. 196 | /// Otherwise, throws an [ArgumentError]. 197 | /// 198 | /// Information about the endpoint, parameters, response etc. can be found at: 199 | /// 200 | /// https://openrouteservice.org/dev/#/api-docs/geocode/search/structured/get 201 | /// 202 | /// https://github.com/pelias/documentation/blob/master/structured-geocoding.md#structured-geocoding 203 | Future geocodeSearchStructuredGet({ 204 | String? address, 205 | String? neighbourhood, 206 | String? country, 207 | String? postalcode, 208 | String? region, 209 | String? county, 210 | String? locality, 211 | String? borough, 212 | ORSCoordinate? focusPointCoordinate, 213 | ORSCoordinate? boundaryRectangleMinCoordinate, 214 | ORSCoordinate? boundaryRectangleMaxCoordinate, 215 | ORSCoordinate? boundaryCircleCoordinate, 216 | double boundaryCircleRadius = 50, 217 | String? boundaryCountry, 218 | List? sources, 219 | List layers = const [], 220 | int size = 10, 221 | }) async { 222 | // Validate input 223 | if (address == null && 224 | neighbourhood == null && 225 | country == null && 226 | postalcode == null && 227 | region == null && 228 | county == null && 229 | locality == null && 230 | borough == null) { 231 | throw ArgumentError( 232 | 'At least one of the following fields is required: venue, address, ' 233 | 'neighbourhood, borough, locality, county, region, postalcode, country', 234 | ); 235 | } 236 | _bothOrNeitherNullValidation( 237 | boundaryRectangleMinCoordinate, 238 | boundaryRectangleMaxCoordinate, 239 | ); 240 | // Form input from parameters. 241 | sources ??= geocodeSourcesAvailable.toList(); 242 | final String sourcesString = _validateAndJoinList( 243 | inputArr: sources, 244 | availableIterable: geocodeSourcesAvailable, 245 | ); 246 | final String layersString = _validateAndJoinList( 247 | inputArr: layers, 248 | availableIterable: geocodeLayersAvailable, 249 | ); 250 | String getURIString = 251 | '$_geocodeEndpointURL/search/structured?api_key=$_apiKey&sources=$sourcesString&size=$size'; 252 | if (layersString.isNotEmpty) { 253 | getURIString += '&layers=$layersString'; 254 | } 255 | if (address != null) { 256 | getURIString += '&address=$address'; 257 | } 258 | if (neighbourhood != null) { 259 | getURIString += '&neighbourhood=$neighbourhood'; 260 | } 261 | if (country != null) { 262 | getURIString += '&country=$country'; 263 | } 264 | if (postalcode != null) { 265 | getURIString += '&postalcode=$postalcode'; 266 | } 267 | if (region != null) { 268 | getURIString += '®ion=$region'; 269 | } 270 | if (county != null) { 271 | getURIString += '&county=$county'; 272 | } 273 | if (locality != null) { 274 | getURIString += '&locality=$locality'; 275 | } 276 | if (borough != null) { 277 | getURIString += '&borough=$borough'; 278 | } 279 | if (focusPointCoordinate != null) { 280 | getURIString += '&focus.point.lon=${focusPointCoordinate.longitude}'; 281 | getURIString += '&focus.point.lat=${focusPointCoordinate.latitude}'; 282 | } 283 | if (boundaryRectangleMinCoordinate != null) { 284 | getURIString += 285 | '&boundary.rect.min_lon=${boundaryRectangleMinCoordinate.longitude}'; 286 | getURIString += 287 | '&boundary.rect.min_lat=${boundaryRectangleMinCoordinate.latitude}'; 288 | } 289 | if (boundaryRectangleMaxCoordinate != null) { 290 | getURIString += 291 | '&boundary.rect.max_lon=${boundaryRectangleMaxCoordinate.longitude}'; 292 | getURIString += 293 | '&boundary.rect.max_lat=${boundaryRectangleMaxCoordinate.latitude}'; 294 | } 295 | if (boundaryCircleCoordinate != null) { 296 | getURIString += 297 | '&boundary.circle.lon=${boundaryCircleCoordinate.longitude}'; 298 | getURIString += 299 | '&boundary.circle.lat=${boundaryCircleCoordinate.latitude}'; 300 | getURIString += '&boundary.circle.radius=$boundaryCircleRadius'; 301 | } 302 | if (boundaryCountry != null) { 303 | getURIString += '&boundary.country=$boundaryCountry'; 304 | } 305 | 306 | // Build the request URL. 307 | final Uri uri = Uri.parse(getURIString); 308 | final Map data = await _openRouteServiceGet(uri: uri); 309 | return GeoJsonFeatureCollection.fromJson(data); 310 | } 311 | 312 | /// Fetches the Reverse Geocode data from the given [sources] and using 313 | /// settings [layers], and returns the entire geojson 314 | /// [GeoJsonFeatureCollection] containing the data. 315 | /// 316 | /// [GeoJsonFeatureCollection] -> [GeoJsonFeatureCollection.features] 317 | /// is a list of [GeoJsonFeature]s whose [GeoJsonFeature.properties] have all 318 | /// the information about the result of the reverse geocoding. 319 | /// 320 | /// Information about the endpoint, parameters, response etc. can be found at: 321 | /// 322 | /// https://openrouteservice.org/dev/#/api-docs/geocode/reverse/get 323 | /// 324 | /// https://github.com/pelias/documentation/blob/master/reverse.md#reverse-geocoding 325 | Future geocodeReverseGet({ 326 | required ORSCoordinate point, 327 | double boundaryCircleRadius = 1, 328 | int size = 10, 329 | List layers = const [], 330 | List? sources, 331 | String? boundaryCountry, 332 | }) async { 333 | // Form Input from parameters. 334 | sources ??= geocodeSourcesAvailable.toList(); 335 | final String sourcesString = _validateAndJoinList( 336 | inputArr: sources, 337 | availableIterable: geocodeSourcesAvailable, 338 | ); 339 | final String layersString = _validateAndJoinList( 340 | inputArr: layers, 341 | availableIterable: geocodeLayersAvailable, 342 | ); 343 | 344 | String getURIString = 345 | '$_geocodeEndpointURL/reverse?api_key=$_apiKey&sources=$sourcesString'; 346 | if (layersString.isNotEmpty) { 347 | getURIString += '&layers=$layersString'; 348 | } 349 | if (boundaryCountry != null) { 350 | getURIString += '&boundary.country=$boundaryCountry'; 351 | } 352 | getURIString += '&point.lon=${point.longitude}&point.lat=${point.latitude}'; 353 | getURIString += '&boundary.circle.radius=$boundaryCircleRadius'; 354 | getURIString += '&size=$size'; 355 | 356 | // Build the request URL. 357 | final Uri uri = Uri.parse(getURIString); 358 | final Map data = await _openRouteServiceGet(uri: uri); 359 | return GeoJsonFeatureCollection.fromJson(data); 360 | } 361 | 362 | /// Validates whether each element of [inputArr] is in [availableIterable]. 363 | /// 364 | /// If so, returns a [String] of the elements joined by a comma that can be 365 | /// used in a URI. 366 | /// 367 | /// If not, throws an [ArgumentError]. 368 | String _validateAndJoinList({ 369 | required List inputArr, 370 | required Iterable availableIterable, 371 | }) { 372 | for (String inp in inputArr) { 373 | if (!availableIterable.contains(inp)) { 374 | throw ArgumentError.value( 375 | inp, 376 | 'inputArr', 377 | 'Input Array must be one of ${availableIterable.join(', ')}', 378 | ); 379 | } 380 | } 381 | return inputArr.join(','); 382 | } 383 | 384 | /// Validates whether either both parameters are none or neither parameter is 385 | /// null. 386 | /// 387 | /// If not, then throws an [ArgumentError]. 388 | void _bothOrNeitherNullValidation(dynamic a, dynamic b) { 389 | final bool validateMinMax = 390 | (a == null && b == null) || (a != null && b != null); 391 | if (!validateMinMax) { 392 | throw ArgumentError( 393 | 'Either both parameters must be null, or neither must be null!', 394 | ); 395 | } 396 | } 397 | } 398 | -------------------------------------------------------------------------------- /lib/src/models/optimization_models/vroom_data_models.dart: -------------------------------------------------------------------------------- 1 | import 'package:open_route_service/src/models/coordinate_model.dart'; 2 | 3 | /// Class Representing a Vroom API Vehicle model required as input to the 4 | /// optimization API endpoint. 5 | /// 6 | /// Full Data Schema available here: 7 | /// https://github.com/VROOM-Project/vroom/blob/master/docs/API.md#vehicles 8 | class VroomVehicle { 9 | const VroomVehicle({ 10 | required this.id, 11 | this.profile = 'driving-car', 12 | this.description, 13 | this.start, 14 | this.startIndex, 15 | this.end, 16 | this.endIndex, 17 | this.capacity, 18 | this.skills, 19 | this.timeWindow, 20 | this.breaks, 21 | this.speedFactor = 1, 22 | this.maxTasks, 23 | this.steps, 24 | }) : assert(start != null || end != null); 25 | 26 | /// Generates a [VroomVehicle] from a [Map] having keys matching the Vroom API 27 | /// Vehicle model that includes: 28 | /// 29 | /// 'id', 'profile', 'description', 'start', 'start_index', 'end', 30 | /// 'end_index', 'capacity', 'skills', 'time_window', 'breaks', 31 | /// 'speed_factor', 'max_tasks', 'steps'. 32 | factory VroomVehicle.fromJson(Map json) => VroomVehicle( 33 | id: json['id'], 34 | profile: json['profile'], 35 | description: json['description'], 36 | start: ORSCoordinate.fromList(json['start']), 37 | startIndex: json['start_index'], 38 | end: ORSCoordinate.fromList(json['end']), 39 | endIndex: json['end_index'], 40 | capacity: json['capacity'] == null 41 | ? null 42 | : (json['capacity'] as List) 43 | .map((dynamic e) => e as int) 44 | .toList(), 45 | skills: json['skills'] == null 46 | ? null 47 | : (json['skills'] as List) 48 | .map((dynamic e) => e as int) 49 | .toList(), 50 | timeWindow: json['time_window'] == null 51 | ? null 52 | : (json['time_window'] as List) 53 | .map((dynamic e) => e as int) 54 | .toList(), 55 | breaks: json['breaks'] == null 56 | ? null 57 | : (json['breaks'] as List) 58 | .map( 59 | (dynamic e) => VroomVehicleBreak.fromJson(e), 60 | ) 61 | .toList(), 62 | speedFactor: json['speed_factor'], 63 | maxTasks: json['max_tasks'], 64 | steps: json['steps'] == null 65 | ? null 66 | : (json['steps'] as List) 67 | .map( 68 | (dynamic e) => VroomVehicleStep.fromJson(e), 69 | ) 70 | .toList(), 71 | ); 72 | 73 | /// Unique [int] identifier for this vehicle. 74 | final int id; 75 | 76 | /// [String] describing this vehicle. 77 | final String? description; 78 | 79 | /// [String] describing the routing profile for this vehicle. 80 | final String profile; 81 | 82 | /// [ORSCoordinate] object describing the start location of this vehicle. 83 | /// 84 | /// [start] and [end] are optional for a vehicle, as long as at least one of 85 | /// them is present. If start is omitted, the resulting route will start at 86 | /// the first visited task, whose choice is determined by the optimization 87 | /// process. 88 | /// 89 | /// To request a round trip, just specify both [start] and [end] with the same 90 | /// coordinates. 91 | final ORSCoordinate? start; 92 | 93 | /// [int] Start index of relevant row and column in custom matrices. 94 | final int? startIndex; 95 | 96 | /// [ORSCoordinate] object describing the end location of this vehicle. 97 | /// 98 | /// [start] and [end] are optional for a vehicle, as long as at least one of 99 | /// them is present. If end is omitted, the resulting route will stop at the 100 | /// last visited task, whose choice is determined by the optimization process 101 | /// 102 | /// To request a round trip, just specify both [start] and [end] with the same 103 | /// coordinates. 104 | final ORSCoordinate? end; 105 | 106 | /// [int] End index of relevant row and column in custom matrices. 107 | final int? endIndex; 108 | 109 | /// [List] of [int] describing multidimensional quantities. 110 | final List? capacity; 111 | 112 | /// [List] of [int] defining skills. 113 | final List? skills; 114 | 115 | /// [List] of [int] Time window describing working hours. 116 | final List? timeWindow; 117 | 118 | /// [List] of [VroomVehicleBreak] describing breaks. 119 | final List? breaks; 120 | 121 | /// [double] value used to scale all vehicle travel times. (Defaults to 1) 122 | /// The respected precision is limited to two digits after the decimal point. 123 | final double? speedFactor; 124 | 125 | /// [int] defining the maximum number of tasks in a route for this vehicle. 126 | final int? maxTasks; 127 | 128 | /// [List] of [VroomVehicleStep] describing a custom route for this vehicle. 129 | final List? steps; 130 | 131 | /// [Map] representation of this [VroomVehicle] object having keys: 'id', 132 | /// 'profile', 'description', 'start', 'start_index', 'end', 'end_index', 133 | /// 'capacity', 'skills', 'time_window', 'breaks', 'speed_factor', 'max_tasks' 134 | /// and 'steps'. 135 | Map toJson() => { 136 | 'id': id, 137 | 'profile': profile, 138 | 'description': description, 139 | 'start': start?.toList(), 140 | 'start_index': startIndex, 141 | 'end': end?.toList(), 142 | 'end_index': endIndex, 143 | 'capacity': capacity, 144 | 'skills': skills, 145 | 'time_window': timeWindow, 146 | 'breaks': breaks 147 | ?.map>((VroomVehicleBreak b) => b.toJson()) 148 | .toList(), 149 | 'speed_factor': speedFactor, 150 | 'max_tasks': maxTasks, 151 | 'steps': steps 152 | ?.map>( 153 | (VroomVehicleStep step) => step.toJson(), 154 | ) 155 | .toList(), 156 | }..removeWhere((String _, dynamic value) => value == null); 157 | 158 | @override 159 | String toString() => toJson().toString(); 160 | 161 | @override 162 | bool operator ==(Object other) => other is VroomVehicle && id == other.id; 163 | 164 | @override 165 | int get hashCode => id.hashCode; 166 | } 167 | 168 | /// Class Representing a Vroom API Job model required as input to the 169 | /// optimization API endpoint. 170 | /// 171 | /// Full Data Schema available here: 172 | /// https://github.com/VROOM-Project/vroom/blob/master/docs/API.md#jobs 173 | class VroomJob { 174 | const VroomJob({ 175 | required this.id, 176 | this.location, 177 | this.description, 178 | this.locationIndex, 179 | this.setup = 0, 180 | this.service = 0, 181 | this.amount, 182 | this.delivery, 183 | this.pickup, 184 | this.skills, 185 | this.priority = 0, 186 | this.timeWindows, 187 | }) : assert(priority == null || (priority >= 0 && priority <= 100)); 188 | 189 | /// Generates a VroomJob from a [Map] having keys matching the Vroom API Job 190 | /// model that includes: 191 | /// 192 | /// 'id', 'description', 'location', 'location_index', 'setup', 'service', 193 | /// 'amount', 'delivery', 'pickup', 'skills', 'priority', and 'time_windows'. 194 | factory VroomJob.fromJson(Map json) => VroomJob( 195 | id: json['id'], 196 | description: json['description'], 197 | location: ORSCoordinate.fromList(json['location']), 198 | locationIndex: json['location_index'], 199 | setup: json['setup'], 200 | service: json['service'], 201 | amount: json['amount'] == null 202 | ? null 203 | : (json['amount'] as List) 204 | .map((dynamic e) => e as int) 205 | .toList(), 206 | delivery: json['delivery'] == null 207 | ? null 208 | : (json['delivery'] as List) 209 | .map((dynamic e) => e as int) 210 | .toList(), 211 | pickup: json['pickup'] == null 212 | ? null 213 | : (json['pickup'] as List) 214 | .map((dynamic e) => e as int) 215 | .toList(), 216 | skills: json['skills'] == null 217 | ? null 218 | : (json['skills'] as List) 219 | .map((dynamic e) => e as int) 220 | .toList(), 221 | priority: json['priority'], 222 | timeWindows: json['time_windows'] == null 223 | ? null 224 | : (json['time_windows'] as List) 225 | .map>( 226 | (dynamic timeWindow) => (timeWindow as List) 227 | .map((dynamic time) => time as int) 228 | .toList(), 229 | ) 230 | .toList(), 231 | ); 232 | 233 | /// Unique [int] identifier for this job. 234 | final int id; 235 | 236 | /// [String] describing this job. 237 | final String? description; 238 | 239 | /// [ORSCoordinate] describing the location of this job. 240 | final ORSCoordinate? location; 241 | 242 | /// [int] index of relevant row and column in custom matrices. 243 | final int? locationIndex; 244 | 245 | /// [int] describing the setup duration of this job. Defaults to 0. 246 | final int? setup; 247 | 248 | /// [int] describing the service duration of this job. Defaults to 0. 249 | final int? service; 250 | 251 | /// [List] of [int] describing multidimensional quantities of this job for 252 | /// amount. 253 | final List? amount; 254 | 255 | /// [List] of [int] describing multidimensional quantities of this job for 256 | /// delivery. 257 | final List? delivery; 258 | 259 | /// [List] of [int] describing multidimensional quantities of this job for 260 | /// pickup. 261 | final List? pickup; 262 | 263 | /// [List] of [int] describing mandatory skills of this job. 264 | final List? skills; 265 | 266 | /// [int] describing the priority of this job. VALID RANGE: [0, 100]. 267 | /// Defaults to 0. 268 | final int? priority; 269 | 270 | /// [List] of time windows describing the valid time windows for this job. 271 | final List>? timeWindows; 272 | 273 | /// [Map] representation of this [VroomJob] having keys 'id', 'description', 274 | /// 'location', 'location_index', 'setup', 'service', 'amount', 'delivery', 275 | /// 'pickup', 'skills', 'priority', and 'time_windows' 276 | Map toJson() => { 277 | 'id': id, 278 | 'description': description, 279 | 'location': location?.toList(), 280 | 'location_index': locationIndex, 281 | 'setup': setup, 282 | 'service': service, 283 | 'amount': amount, 284 | 'delivery': delivery, 285 | 'pickup': pickup, 286 | 'skills': skills, 287 | 'priority': priority, 288 | 'time_windows': timeWindows, 289 | }..removeWhere((String _, dynamic value) => value == null); 290 | 291 | @override 292 | String toString() => toJson().toString(); 293 | 294 | @override 295 | bool operator ==(Object other) => other is VroomJob && id == other.id; 296 | 297 | @override 298 | int get hashCode => id.hashCode; 299 | } 300 | 301 | /// Class Representing a Vroom API Vehicle Break Model. 302 | /// 303 | /// View the Vehicles Section to find the Break Model schema. 304 | /// https://github.com/VROOM-Project/vroom/blob/master/docs/API.md#vehicles 305 | class VroomVehicleBreak { 306 | const VroomVehicleBreak({ 307 | required this.id, 308 | this.timeWindows, 309 | this.service = 0, 310 | this.description, 311 | }); 312 | 313 | /// Generates a VroomVehicleBreak from a [Map] having keys matching the Vroom 314 | /// API Vehicle Break model that includes: 315 | /// 316 | /// 'id', 'time_windows', 'service', and 'description'. 317 | factory VroomVehicleBreak.fromJson(Map json) => 318 | VroomVehicleBreak( 319 | id: json['id'], 320 | timeWindows: json['time_windows'] == null 321 | ? null 322 | : (json['time_windows'] as List) 323 | .map>( 324 | (dynamic timeWindow) => (timeWindow as List) 325 | .map((dynamic time) => time as int) 326 | .toList(), 327 | ) 328 | .toList(), 329 | service: json['service'] ?? 0, 330 | description: json['description'], 331 | ); 332 | 333 | /// Unique [int] identifier for this break. 334 | final int id; 335 | 336 | /// [List] of time windows describing the valid time windows for this break. 337 | final List>? timeWindows; 338 | 339 | /// [num] describing the service duration of this break. Defaults to 0. 340 | final int service; 341 | 342 | /// [String] describing this break. 343 | final String? description; 344 | 345 | /// [Map] representation of this [VroomVehicleBreak] having keys 'id', 346 | /// 'time_windows', 'service', and 'description'. 347 | Map toJson() => { 348 | 'id': id, 349 | 'time_windows': timeWindows, 350 | 'service': service, 351 | 'description': description, 352 | }; 353 | 354 | @override 355 | String toString() => toJson().toString(); 356 | 357 | @override 358 | bool operator ==(Object other) => 359 | other is VroomVehicleBreak && id == other.id; 360 | 361 | @override 362 | int get hashCode => id.hashCode; 363 | } 364 | 365 | /// Class Representing a Vroom API Vehicle Step Model. 366 | /// 367 | /// [type] a string (either start, job, pickup, delivery, break or end) 368 | /// 369 | /// [id] id of the task to be performed at this step if type value is job, 370 | /// pickup, delivery or break 371 | /// 372 | /// [service_at] hard constraint on service time 373 | /// 374 | /// [service_after] hard constraint on service time lower bound 375 | /// 376 | /// [service_before] hard constraint on service time upper bound 377 | /// 378 | /// View the Vehicles Section to find the Step Model schema. 379 | /// https://github.com/VROOM-Project/vroom/blob/master/docs/API.md#vehicles 380 | class VroomVehicleStep { 381 | const VroomVehicleStep({ 382 | required this.id, 383 | required this.type, 384 | this.serviceAt, 385 | this.serviceAfter, 386 | this.serviceBefore, 387 | }); 388 | 389 | /// Generates a VroomVehicleStep from a [Map] having keys matching the Vroom 390 | /// API Vehicle Step model that includes: 391 | /// 392 | /// 'type', 'id', 'service_at', 'service_after', and 'service_before'. 393 | factory VroomVehicleStep.fromJson(Map json) => 394 | VroomVehicleStep( 395 | type: vroomVehicleStepTypeFromString(json['type'] as String), 396 | id: json['id'], 397 | serviceAt: json['service_at'], 398 | serviceAfter: json['service_after'], 399 | serviceBefore: json['service_before'], 400 | ); 401 | 402 | /// [String] describing the type of this step. 403 | final VroomVehicleStepType type; 404 | 405 | /// [int] identifier for this step. 406 | final int id; 407 | 408 | /// [int] describing the hard constraint on service time. 409 | final int? serviceAt; 410 | 411 | /// [int] describing the hard constraint on service time lower bound. 412 | final int? serviceAfter; 413 | 414 | /// [int] describing the hard constraint on service time upper bound. 415 | final int? serviceBefore; 416 | 417 | /// Converts enum [VroomVehicleStepType] to corresponding [String]. 418 | static String vroomVehicleStepTypeToString(VroomVehicleStepType type) { 419 | switch (type) { 420 | case VroomVehicleStepType.start: 421 | return 'start'; 422 | 423 | case VroomVehicleStepType.job: 424 | return 'job'; 425 | 426 | case VroomVehicleStepType.pickup: 427 | return 'pickup'; 428 | 429 | case VroomVehicleStepType.delivery: 430 | return 'delivery'; 431 | 432 | case VroomVehicleStepType.break_: 433 | return 'break'; 434 | 435 | case VroomVehicleStepType.end: 436 | return 'end'; 437 | 438 | default: 439 | throw ArgumentError('Invalid VroomVehicleStepType: $type'); 440 | } 441 | } 442 | 443 | /// Converts [String] to corresponding enum [VroomVehicleStepType]. 444 | static VroomVehicleStepType vroomVehicleStepTypeFromString(String type) { 445 | switch (type) { 446 | case 'start': 447 | return VroomVehicleStepType.start; 448 | 449 | case 'job': 450 | return VroomVehicleStepType.job; 451 | 452 | case 'pickup': 453 | return VroomVehicleStepType.pickup; 454 | 455 | case 'delivery': 456 | return VroomVehicleStepType.delivery; 457 | 458 | case 'break': 459 | return VroomVehicleStepType.break_; 460 | 461 | case 'end': 462 | return VroomVehicleStepType.end; 463 | 464 | default: 465 | throw ArgumentError('Invalid VroomVehicleStepType: $type'); 466 | } 467 | } 468 | 469 | /// [Map] representation of this [VroomVehicleStep] having keys 'type', 470 | /// 'id', 'service_at', 'service_after', and 'service_before'. 471 | Map toJson() => { 472 | 'type': vroomVehicleStepTypeToString(type), 473 | 'id': id, 474 | 'service_at': serviceAt, 475 | 'service_after': serviceAfter, 476 | 'service_before': serviceBefore, 477 | }; 478 | 479 | @override 480 | String toString() => toJson().toString(); 481 | 482 | @override 483 | bool operator ==(Object other) => 484 | other is VroomVehicleStep && id == other.id && type == other.type; 485 | 486 | @override 487 | int get hashCode => id.hashCode ^ type.hashCode; 488 | } 489 | 490 | /// Enums of the Vroom Vehicle Step Types. 491 | /// 492 | /// Corresponds to possible [VroomVehicleStep.type] values: 493 | /// 'start', 'job', 'pickup', 'delivery', 'break' and 'end' respectively. 494 | enum VroomVehicleStepType { 495 | start, 496 | job, 497 | pickup, 498 | delivery, 499 | break_, 500 | end, 501 | } 502 | --------------------------------------------------------------------------------