diff --git a/example/assets/map/epsg3413/amsr2.png b/example/assets/map/epsg3413/amsr2.png deleted file mode 100644 index ad24c5e91..000000000 Binary files a/example/assets/map/epsg3413/amsr2.png and /dev/null differ diff --git a/example/lib/main.dart b/example/lib/main.dart index 93f2ce9e7..b0c9b03c9 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -3,9 +3,8 @@ import 'package:flutter_map_example/pages/animated_map_controller.dart'; import 'package:flutter_map_example/pages/bundled_offline_map.dart'; import 'package:flutter_map_example/pages/cancellable_tile_provider.dart'; import 'package:flutter_map_example/pages/circle.dart'; -import 'package:flutter_map_example/pages/custom_crs/custom_crs.dart'; import 'package:flutter_map_example/pages/debouncing_tile_update_transformer.dart'; -import 'package:flutter_map_example/pages/epsg3413_crs.dart'; +import 'package:flutter_map_example/pages/epsg3996_crs.dart'; import 'package:flutter_map_example/pages/epsg4326_crs.dart'; import 'package:flutter_map_example/pages/fallback_url_page.dart'; import 'package:flutter_map_example/pages/home.dart'; @@ -70,7 +69,6 @@ class MyApp extends StatelessWidget { PolygonPerfStressPage.route: (context) => const PolygonPerfStressPage(), SlidingMapPage.route: (_) => const SlidingMapPage(), WMSLayerPage.route: (context) => const WMSLayerPage(), - CustomCrsPage.route: (context) => const CustomCrsPage(), TileLoadingErrorHandle.route: (context) => const TileLoadingErrorHandle(), TileBuilderPage.route: (context) => const TileBuilderPage(), @@ -79,7 +77,7 @@ class MyApp extends StatelessWidget { MapInsideListViewPage.route: (context) => const MapInsideListViewPage(), ResetTileLayerPage.route: (context) => const ResetTileLayerPage(), EPSG4326Page.route: (context) => const EPSG4326Page(), - EPSG3413Page.route: (context) => const EPSG3413Page(), + EPSG3996Page.route: (context) => const EPSG3996Page(), ScreenPointToLatLngPage.route: (context) => const ScreenPointToLatLngPage(), LatLngToScreenPointPage.route: (context) => diff --git a/example/lib/pages/custom_crs/Readme.md b/example/lib/pages/custom_crs/Readme.md deleted file mode 100644 index a79854052..000000000 --- a/example/lib/pages/custom_crs/Readme.md +++ /dev/null @@ -1,139 +0,0 @@ -# Custom CRS - -## Projection - -To define a `Proj4Crs` (custom CRS) you have to register a projection of `proj4.Projection`. For -that you must import `proj4dart` library as follows: - -```dart -import 'package:proj4dart/proj4dart.dart' as proj4; -``` - -You can create and register your custom projection in multiple ways, but the recommended is to use -a **Proj4 definition string** from [epsg.io](https://epsg.io). For example for `EPSG:3413` (_WGS 84 -/ NSIDC Sea Ice Polar Stereographic North_) you can find it [here](https://epsg.io/3413.proj4). This -is how a Proj4 definition string looks like: - -```dart -+proj=stere +lat_0=90 +lat_ts=70 +lon_0=-45 +k=1 +x_0=0 +y_0=0 +datum=WGS84 -+ -units -= -m -+ -no_defs -``` - -With this **Proj4 definition string** and a **string identifier** register your `proj4.Projection` -like this: - -```dart - -var customProjection = proj4.Projection.add('EPSG:3413', - '+proj=stere +lat_0=90 +lat_ts=70 +lon_0=-45 +k=1 +x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs'); -``` - -For more possible ways to register `proj4.Projection` -see [proj4dart documentation](https://github.com/maRci002/proj4dart). - -## Coordinate Reference System (CRS) - -You can use your previously registered `proj4.Projection` to create a custom CRS of type `Proj4Crs`. -You can use the following parameters: - -- `` `code` (required): string identifier for the selected CRS, e.g. `EPSG:3413` -- `` `proj4Projection` (required): the `proj4.Projection` object you wish to use -- `` `bounds`: bounds of the CRS in projected coordinates -- `>` `resolutions`: an array of zoom factors (projection units per pixel, eg. - meters/pixel) -- `>` `scales`: scale factors (pixels per projection unit); specify either scales or - resolutions, but not both! -- `>` `origins`: tile origin in projected coordinates (for TileLayer). Why is it needed? - GeoServer by default can define different origins (top left coordinates) for each zoom levels. In - case of origin mismatch the tile will be drawn on the wrong place: the map will jump at such zoom - levels. If your origins vary with zoom levels the number of origins must match the number of - resolutions. You can get the desired origins from a `GetCapabilities` WMTS call from geoserver - e.g. `http://[ip:port]/geoserver/gwc/service/wmts?request=GetCapabilities`. This results an XML, - and you have to look up for the `TopLeftCorner`s for each TileMatrix of your TileMatrixSet. - ![Tile Origins](./origins.png) -- `` `transformation`: the transformation to use when transforming projected - coordinates into pixel coordinates - -An example: - -```dart - -var epsg3413 = proj4.Projection.add('EPSG:3413', - '+proj=stere +lat_0=90 +lat_ts=70 +lon_0=-45 +k=1 +x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs'); - -final resolutions = [ - 32768, - 16384, - 8192, - 4096, - 2048, - 1024, - 512, - 256, - 128, -]; - -final epsg3413Bounds = Bounds( - Point(-4511619.0, -4511336.0), - Point(4510883.0, 4510996.0), -); - -var maxZoom = (resolutions.length - 1).toDouble(); - -var epsg3413CRS = Proj4Crs.fromFactory( - code: 'EPSG:3413', - proj4Projection: epsg3413, - resolutions: resolutions, - bounds: epsg3413Bounds, - origins: [Point(0, 0)], - scales: null, - transformation: null, -); -``` - -## Usage - -Proj4Crs has multiple uses: - -- Set `FlutterMap`'s default CRS: - - ```dart - FlutterMap( - options: MapOptions( - // Set the default CRS - crs: epsg3413CRS, - center: LatLng(65.05166470332148, -19.171744826394896), - zoom: 3.0, - maxZoom: maxZoom, - ), - layers: [], - ); - ``` - -- Set a WMS layer's CRS - - ```dart - TileLayerOptions( - opacity: 1.0, - backgroundColor: Colors.transparent, - wmsOptions: WMSTileLayerOptions( - // Set the WMS layer's CRS - crs: epsg3413CRS, - transparent: true, - format: 'image/jpeg', - baseUrl: - 'https://www.gebco.net/data_and_products/gebco_web_services/north_polar_view_wms/mapserv?', - layers: ['gebco_north_polar_view'], - ), - ); - ``` - -For complete code (with point transformation from one projection to another) see the page source -code. This is how it looks like: - -![Custom CRS](./custom_crs.png) diff --git a/example/lib/pages/custom_crs/custom_crs.dart b/example/lib/pages/custom_crs/custom_crs.dart deleted file mode 100644 index 9d7589e35..000000000 --- a/example/lib/pages/custom_crs/custom_crs.dart +++ /dev/null @@ -1,173 +0,0 @@ -import 'dart:math'; - -import 'package:flutter/material.dart'; -import 'package:flutter_map/flutter_map.dart'; -import 'package:flutter_map_example/widgets/drawer/menu_drawer.dart'; -import 'package:latlong2/latlong.dart'; -import 'package:proj4dart/proj4dart.dart' as proj4; -import 'package:url_launcher/url_launcher.dart'; - -class CustomCrsPage extends StatefulWidget { - static const String route = '/crs_custom'; - - const CustomCrsPage({super.key}); - - @override - CustomCrsPageState createState() => CustomCrsPageState(); -} - -class CustomCrsPageState extends State { - late final Proj4Crs epsg3413CRS; - - double? maxZoom; - - // Define start center - proj4.Point point = proj4.Point(x: 65.05166470332148, y: -19.171744826394896); - - String initText = 'Map centered to'; - - late final proj4.Projection epsg4326; - - late final proj4.Projection epsg3413; - - @override - void initState() { - super.initState(); - - // EPSG:4326 is a predefined projection ships with proj4dart - epsg4326 = proj4.Projection.get('EPSG:4326')!; - - // EPSG:3413 is a user-defined projection from a valid Proj4 definition string - // From: http://epsg.io/3413, proj definition: http://epsg.io/3413.proj4 - // Find Projection by name or define it if not exists - epsg3413 = proj4.Projection.get('EPSG:3413') ?? - proj4.Projection.add('EPSG:3413', - '+proj=stere +lat_0=90 +lat_ts=70 +lon_0=-45 +k=1 +x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs'); - - // 9 example zoom level resolutions - final resolutions = [ - 32768, - 16384, - 8192, - 4096, - 2048, - 1024, - 512, - 256, - 128, - ]; - - final epsg3413Bounds = Bounds( - const Point(-4511619, -4511336), - const Point(4510883, 4510996), - ); - - maxZoom = (resolutions.length - 1).toDouble(); - - // Define CRS - epsg3413CRS = Proj4Crs.fromFactory( - // CRS code - code: 'EPSG:3413', - // your proj4 delegate - proj4Projection: epsg3413, - // Resolution factors (projection units per pixel, for example meters/pixel) - // for zoom levels; specify either scales or resolutions, not both - resolutions: resolutions, - // Bounds of the CRS, in projected coordinates - // (if not specified, the layer's which uses this CRS will be infinite) - bounds: epsg3413Bounds, - // Tile origin, in projected coordinates, if set, this overrides the transformation option - // Some goeserver changes origin based on zoom level - // and some are not at all (use explicit/implicit null or use [Point(0, 0)]) - // @see https://github.com/kartena/Proj4Leaflet/pull/171 - origins: const [Point(0, 0)], - // Scale factors (pixels per projection unit, for example pixels/meter) for zoom levels; - // specify either scales or resolutions, not both - scales: null, - ); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar(title: const Text('Custom CRS')), - drawer: const MenuDrawer(CustomCrsPage.route), - body: Padding( - padding: const EdgeInsets.all(8), - child: Column( - children: [ - const Padding( - padding: EdgeInsets.only(top: 8, bottom: 2), - child: Text( - 'This map is in EPSG:3413', - style: TextStyle( - fontWeight: FontWeight.bold, - color: Colors.blue, - fontSize: 16, - ), - ), - ), - Padding( - padding: const EdgeInsets.only(top: 8, bottom: 2), - child: Text( - '$initText (${point.x.toStringAsFixed(5)}, ${point.y.toStringAsFixed(5)}) in EPSG:4326.', - ), - ), - Padding( - padding: const EdgeInsets.only(top: 2, bottom: 2), - child: Text( - 'Which is (${epsg4326.transform(epsg3413, point).x.toStringAsFixed(2)}, ${epsg4326.transform(epsg3413, point).y.toStringAsFixed(2)}) in EPSG:3413.', - ), - ), - const Padding( - padding: EdgeInsets.only(top: 2, bottom: 8), - child: Text('Tap on map to get more coordinates!'), - ), - Flexible( - child: FlutterMap( - options: MapOptions( - // Set the default CRS - crs: epsg3413CRS, - initialCenter: LatLng(point.x, point.y), - initialZoom: 3, - // Set maxZoom usually scales.length - 1 OR resolutions.length - 1 - // but not greater - maxZoom: maxZoom, - onTap: (tapPosition, p) => setState(() { - initText = 'You clicked at'; - point = proj4.Point(x: p.latitude, y: p.longitude); - }), - ), - children: [ - TileLayer( - wmsOptions: WMSTileLayerOptions( - crs: epsg3413CRS, - transparent: true, - format: 'image/jpeg', - baseUrl: - 'https://www.gebco.net/data_and_products/gebco_web_services/north_polar_view_wms/mapserv?', - layers: const ['gebco_north_polar_view'], - ), - ), - RichAttributionWidget( - popupInitialDisplayDuration: const Duration(seconds: 5), - attributions: [ - TextSourceAttribution( - 'Imagery reproduced from the GEBCO_2022 Grid, GEBCO Compilation Group (2022) GEBCO 2022 Grid (doi:10.5285/e0f0bb80-ab44-2739-e053-6c86abc0289c)', - onTap: () => launchUrl( - Uri.parse( - 'https://www.gebco.net/data_and_products/gebco_web_services/web_map_service/#polar', - ), - ), - ), - ], - ), - ], - ), - ), - ], - ), - ), - ); - } -} diff --git a/example/lib/pages/custom_crs/custom_crs.png b/example/lib/pages/custom_crs/custom_crs.png deleted file mode 100644 index bd69c3a86..000000000 Binary files a/example/lib/pages/custom_crs/custom_crs.png and /dev/null differ diff --git a/example/lib/pages/custom_crs/origins.png b/example/lib/pages/custom_crs/origins.png deleted file mode 100644 index e11ca7e52..000000000 Binary files a/example/lib/pages/custom_crs/origins.png and /dev/null differ diff --git a/example/lib/pages/epsg3413_crs.dart b/example/lib/pages/epsg3996_crs.dart similarity index 61% rename from example/lib/pages/epsg3413_crs.dart rename to example/lib/pages/epsg3996_crs.dart index b72fb1c36..f2eb97e9b 100644 --- a/example/lib/pages/epsg3413_crs.dart +++ b/example/lib/pages/epsg3996_crs.dart @@ -7,17 +7,17 @@ import 'package:latlong2/latlong.dart'; import 'package:proj4dart/proj4dart.dart' as proj4; import 'package:url_launcher/url_launcher.dart'; -class EPSG3413Page extends StatefulWidget { - static const String route = '/crs_epsg3413'; +class EPSG3996Page extends StatefulWidget { + static const String route = '/crs_epsg3996'; - const EPSG3413Page({super.key}); + const EPSG3996Page({super.key}); @override - EPSG3413PageState createState() => EPSG3413PageState(); + EPSG3996PageState createState() => EPSG3996PageState(); } -class EPSG3413PageState extends State { - late final Proj4Crs epsg3413CRS; +class EPSG3996PageState extends State { + late final Proj4Crs epsg3996CRS; final distancePoleToLat80 = const Distance().distance(const LatLng(90, 0), const LatLng(80, 0)); @@ -48,19 +48,19 @@ class EPSG3413PageState extends State { maxZoom = resolutions.length - 1; - // EPSG:3413 is a user-defined projection from a valid Proj4 definition string - // From: http://epsg.io/3413, proj definition: http://epsg.io/3413.proj4 + // EPSG:3996 is a user-defined projection from a valid Proj4 definition string + // From: http://epsg.io/3996, proj definition: http://epsg.io/3996.proj4 // Find Projection by name or define it if not exists - final proj4.Projection epsg3413 = proj4.Projection.get('EPSG:3413') ?? + final proj4.Projection epsg3996 = proj4.Projection.get('EPSG:3996') ?? proj4.Projection.add( - 'EPSG:3413', - '+proj=stere +lat_0=90 +lat_ts=70 +lon_0=-45 +k=1 +x_0=0 +y_0=0 ' - '+datum=WGS84 +units=m +no_defs', + 'EPSG:3996', + '+proj=stere +lat_0=90 +lat_ts=75 +lon_0=0 +x_0=0 +y_0=0 ' + '+datum=WGS84 +units=m +no_defs +type=crs', ); - epsg3413CRS = Proj4Crs.fromFactory( - code: 'EPSG:3413', - proj4Projection: epsg3413, + epsg3996CRS = Proj4Crs.fromFactory( + code: 'EPSG:3996', + proj4Projection: epsg3996, resolutions: resolutions, bounds: epsg3413Bounds, origins: const [Point(0, 0)], @@ -99,8 +99,8 @@ class EPSG3413PageState extends State { )); return Scaffold( - appBar: AppBar(title: const Text('EPSG:3413 CRS')), - drawer: const MenuDrawer(EPSG3413Page.route), + appBar: AppBar(title: const Text('EPSG:3996 CRS')), + drawer: const MenuDrawer(EPSG3996Page.route), body: Padding( padding: const EdgeInsets.all(8), child: Column( @@ -117,24 +117,26 @@ class EPSG3413PageState extends State { ), ), const Text( - 'Details: https://github.com/fleaflet/flutter_map/pull/1295'), + 'CRS defined manually, not built-in to flutter_map' + '\nDetails: https://github.com/fleaflet/flutter_map/pull/1295', + textAlign: TextAlign.center, + ), const Padding( padding: EdgeInsets.only(top: 8, bottom: 2), child: SizedBox( width: 500, child: Text( - '• Northern and eastern directions are relative to where you are on the map:\n' - ' • A red dot moves north toward the yellow dot (North Pole).\n' - ' • A red dot moves east counter-clockwise along the black latitude line (80°).\n' - '• The lower left and right corners of the overlay image are the northern corners.' - //textAlign: TextAlign.center, - ), + '• Northern and eastern directions are relative to where you are on the map:\n' + ' • A red dot moves north toward the yellow dot (North Pole).\n' + ' • A red dot moves east counter-clockwise along the black latitude line (80°).\n' + '• The lower left and right corners of the overlay image are the northern corners.', + ), ), ), Flexible( child: FlutterMap( options: MapOptions( - crs: epsg3413CRS, + crs: epsg3996CRS, initialCenter: const LatLng(90, 0), initialZoom: 3, maxZoom: maxZoom, @@ -142,35 +144,23 @@ class EPSG3413PageState extends State { children: [ TileLayer( wmsOptions: WMSTileLayerOptions( - crs: epsg3413CRS, + crs: epsg3996CRS, transparent: true, format: 'image/jpeg', baseUrl: - 'https://www.gebco.net/data_and_products/gebco_web_services/north_polar_view_wms/mapserv?', - layers: const ['gebco_north_polar_view'], + 'https://wms.gebco.net/2024/north-polar/mapserv?', + layers: const ['GEBCO_NORTH_POLAR_VIEW_ICE_2024'], ), ), - OverlayImageLayer( - overlayImages: [ - OverlayImage( - bounds: LatLngBounds( - const LatLng(72.7911372, 162.6196478), - const LatLng(85.2802493, 79.794166), - ), - imageProvider: Image.asset( - 'assets/map/epsg3413/amsr2.png', - ).image, - ) - ], - ), CircleLayer(circles: circles), RichAttributionWidget( popupInitialDisplayDuration: const Duration(seconds: 5), attributions: [ TextSourceAttribution( - 'Imagery reproduced from the GEBCO_2022 Grid, GEBCO ' - 'Compilation Group (2022) GEBCO 2022 Grid ' - '(doi:10.5285/e0f0bb80-ab44-2739-e053-6c86abc0289c)', + 'Imagery reproduced from the GEBCO_2024 Grid, GEBCO ' + 'Compilation Group (2024) GEBCO Compilation Group ' + '(2024) GEBCO 2024 Grid ' + '(doi:10.5285/1c44ce99-0a0d-5f4f-e063-7086abc0ea0f)', onTap: () => launchUrl( Uri.parse( 'https://www.gebco.net/data_and_products/gebco_web_services/web_map_service/#polar', diff --git a/example/lib/widgets/drawer/menu_drawer.dart b/example/lib/widgets/drawer/menu_drawer.dart index 14a4cab6b..7e85f08f3 100644 --- a/example/lib/widgets/drawer/menu_drawer.dart +++ b/example/lib/widgets/drawer/menu_drawer.dart @@ -4,9 +4,8 @@ import 'package:flutter_map_example/pages/animated_map_controller.dart'; import 'package:flutter_map_example/pages/bundled_offline_map.dart'; import 'package:flutter_map_example/pages/cancellable_tile_provider.dart'; import 'package:flutter_map_example/pages/circle.dart'; -import 'package:flutter_map_example/pages/custom_crs/custom_crs.dart'; import 'package:flutter_map_example/pages/debouncing_tile_update_transformer.dart'; -import 'package:flutter_map_example/pages/epsg3413_crs.dart'; +import 'package:flutter_map_example/pages/epsg3996_crs.dart'; import 'package:flutter_map_example/pages/epsg4326_crs.dart'; import 'package:flutter_map_example/pages/fallback_url_page.dart'; import 'package:flutter_map_example/pages/home.dart'; @@ -181,19 +180,14 @@ class MenuDrawer extends StatelessWidget { ), const Divider(), MenuItemWidget( - caption: 'Custom CRS', - routeName: CustomCrsPage.route, - currentRoute: currentRoute, - ), - MenuItemWidget( - caption: 'EPSG4326 CRS', + caption: 'EPSG:4326 CRS', currentRoute: currentRoute, routeName: EPSG4326Page.route, ), MenuItemWidget( - caption: 'EPSG3413 CRS', + caption: 'EPSG:3996 (Custom) CRS', currentRoute: currentRoute, - routeName: EPSG3413Page.route, + routeName: EPSG3996Page.route, ), const Divider(), MenuItemWidget( diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 37c2fb06f..6fc340d43 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -68,6 +68,5 @@ flutter: - assets/map/anholt_osmbright/14/8725/ - assets/map/anholt_osmbright/14/8726/ - assets/map/anholt_osmbright/14/8727/ - - assets/map/epsg3413/amsr2.png - assets/mapbox-logo-white.png - assets/