Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Can I use a tile provider that accepts a different crs than the one used by my FlutterMap? #890

Closed
magnuswikhog opened this issue May 7, 2021 · 28 comments

Comments

@magnuswikhog
Copy link

I have a FlutterMap that uses Epsg3857 CRS, but I want to be able to use a tile URL that is supposed to be used with Epsg3006. Is this possible?

I tried creating a custom TileProvider, but I'm unable to convert the Coords from Epsg3857 tile coordinates to Epsg3006 tile coordinates.

Any ideas?

@ibrierley
Copy link
Contributor

I'm not that familiar with CRSs but when you say you are unable to convert the coords, how far did you get.

@ibrierley
Copy link
Contributor

Btw there's a bit about custom CRS on the main docs.. https://github.com/fleaflet/flutter_map half way down

@magnuswikhog
Copy link
Author

magnuswikhog commented May 8, 2021

I'm not that familiar with CRSs but when you say you are unable to convert the coords, how far did you get.

I tried using unproject and project, but that gave wildly incorrect results so I guess I'm using them wrong.

To be clear, converting latitude and longitude is straightforward, there are methods for that in the Epsg3006 class. However, in the TileProvider you only have access to the "tile coordinates" (x/y and zoom), and I don't know how to convert those.

Btw there's a bit about custom CRS on the main docs.. https://github.com/fleaflet/flutter_map half way down

Thanks, I read it but it seems to apply to WMS layers? I'm not exactly sure how these things all relate, but my URL's are on the format https://example.com/tiles/256/{z}/{y}/{x}.png, so not sure if/how I would use that with a WMS layer?

@ibrierley
Copy link
Contributor

Maybe it would be useful to show a link or whatever to which class you are looking at for Epsg3006. May trigger some ideas.

@1075dn
Copy link

1075dn commented May 10, 2021

I have this problem as well. Maybe we should make a pull request to add this feature to standard tile layers?

@ibrierley
Copy link
Contributor

Not sure why it needs a feature added to tile layers, you can pass a custom crs into MapOptions, so wouldn't that be the correct way to go ? Or am I misunderstanding what the point of the option is ?

@1075dn
Copy link

1075dn commented May 10, 2021

You're right, but if we want to use a single FlutterMap widget with multiple layers, each (possibly) with a different CRS (unless I misunderstand MapOptions... 🤣).

@1075dn
Copy link

1075dn commented May 10, 2021

What I'm currently doing is using a stateless widget, and setting the layer and CRS as a parameter.
When you want to change the style to a layer with a different CRS, change both parameters.
(This obviously won't work if you're trying to display two layers with different CRSs simultaneously.)

Simplified code:

final Crs curCrs;
final TileLayerOptions curLayer;
final LatLngBounds initialBounds;

MyMap(this.curCrs, this.curLayer);

Widget build(BuildContext context) {
  return FlutterMap(
    mapController: mapController,
    options: MapOptions(
      crs: curCrs,
      bounds: initialBounds,
      // ...
    ),
    layers: [
      curLayer,
      // ...
    ]
  );
}

@magnuswikhog
Copy link
Author

magnuswikhog commented May 11, 2021

Thanks @1075dn - I tried that, but it seems that other plugins, for example MarkerClusterPlugin don't work properly when doing that? Or am I missing some option that could make it work? I also run into problems with zoom levels and the current bounds being different between the different CRS's, so switching between the two is far from seamless in my case.

@1075dn
Copy link

1075dn commented May 11, 2021

I don't use MarkerClusterPlugin, so I don't know, but when I tried to copy the ScaleBarPlugin from the example into my map with a different CRS it displayed incorrectly.

I also run into problems with zoom levels and the current bounds being different between the different CRS's, so switching between the two is far from seamless in my case.

Yup, me too. I'm still trying to figure out a solution to that. If you do first, let me know 😉!

@1075dn
Copy link

1075dn commented May 12, 2021

I've looked into it a bit.

Since projections are different ways that points on the globe can be mapped to 2D x/y coordinates, it's impossible to display multiple layers projected with separate CRS, one above the next, without either:

  • having them misaligned (possibly by a lot, depending on how different the CRS are), or
  • reprojecting (warping) all of the tiles to a single CRS (which is highly inadvisable as it takes a significant amount of processing, plus you'll need to serve the warped tiles yourself).

Maybe we should make a pull request to add this feature to standard tile layers?

Bad idea, me.

However, people have been able to swap between two different CRS (showing one layer or the other, not both at the same time) with Leaflet (what flutter_map is ported from) like so:

  • Save current bounds to a variable
  • Remove old layer
  • Change CRS
  • Add new layer
  • fitBounds from old bounds variable

Apparently both @magnuswikhog and I are having trouble figuring out how to do this in Flutter. (I tried using a StatelessWidget - see above)
Suggestions, anyone?

@1075dn
Copy link

1075dn commented May 13, 2021

Maybe @kengu can confirm what I wrote about how projections work, and possibly also explain why WMSLayerOptions can have a separate CRS (he suggested supporting that in Leaflet)?

@kengu
Copy link
Contributor

kengu commented May 13, 2021

@1075dn The reason why WMS supports crs in options is because it's part of the WMS standard, see below

Screenshot 2021-05-13 at 11 28 15

where each layer could be projected using different coordinate reference system. The backend is typically either 1) rendering dynamically from vectors using SLD combined with dynamic caching for improved response on repeated calls for same tile, or 2) wms tiles on all zoom levels are preprocessed into each supported projection. Reprojection tiles from one CRS to another on the client-side is less common on raster-formats because of the reasons you outlined above. For vector formats however, projections are always applied and hence it is less of a problem to support different crs there.

@1075dn
Copy link

1075dn commented May 13, 2021

Thanks for the detailed explanation, @kengu! So setting the CRS on a WMSTileLayer explicitly requests the WMS server to project the tiles with the same CRS as the other layers of the map, and does not (like I thought at first) allow a map to have layers with different projections.

@kengu
Copy link
Contributor

kengu commented May 13, 2021

@1075dn Not exactly, it works like you would expect; Tiles returned from the WMS-service is projected using the crs supplied. This is perfectly fine as long as layers visible at the same time (baselayer + overlays) have the same crs. The conversion between screen, geodesic (lat/ln) and projected (i.e cartesian) coordinates uses the crs supplied in the layer, not the crs supplied in MapOptions.

@1075dn
Copy link

1075dn commented May 13, 2021

@kengu Thanks for clarifying! I didn't mean that it forced you to use the MapOptions' CRS. I guess you theoretically could supply WMSLayerOptions with a CRS that is incompatible with the other layers' CRS, but then they would be misaligned, correct?

@1075dn
Copy link

1075dn commented May 13, 2021

Also, by the way @kengu, do you have any solutions to switching the MapOptions' CRS programmatically?

@kengu
Copy link
Contributor

kengu commented May 13, 2021

Correct. This must be managed outside of flutter_map. The same goes for changing MapOptions dynamically, which are always read from the current FlutterMap widget in FlutterMapState, see

Screenshot 2021-05-13 at 15 57 51

All you need to do is to build MapOptions from parameteres controlled outside of FlutterMap in the build method building FlutterMap, forcing rebuilds dynamically using a stateful widget og widget builder of you choice. The naive solution is to wrap FlutterMap with a stateful widget and call setState after changing som parameter that MapOptions are built from.

@1075dn
Copy link

1075dn commented May 13, 2021

Thanks @kengu - that works!
Do you know of any way, after the CRS and layer change, to fitBounds back to the original viewport bounds before the CRS/layer change?
When I switch CRS/layers, the zoom level number stays the same, but the map looks much smaller, since the zoom/scale levels differ between the different Crs-es. That's what I've really been struggling with.

@kengu
Copy link
Contributor

kengu commented May 13, 2021

I use a MapController to handle this. You can ensure zoom stays the same by setting zoom in options equal to zoom in map controller when building a new instance of MapOptions.

edit: I don't know if this helps though on your problem - probably not after some additional thought. You probably need to compensate for the change before setting zoom-level after changing to another crs.

@1075dn
Copy link

1075dn commented May 13, 2021

I guess I need to figure out if there is any way to 'convert' zoom levels between Crs-es... Do you have any idea how to do this?

@ibrierley
Copy link
Contributor

Just thinking out loud, as can't recall what methods are aren't private, but can you get pixelbounds from both crs's and take the ratio difference between them, and then scale the zoom by that ?

@ibrierley
Copy link
Contributor

Erm, not quite sure that makes sense actually...need to look at code before speaking!

@kengu
Copy link
Contributor

kengu commented May 13, 2021

The idea proposed by @ibrierley sounds reasonable. If that does not work out, you probably need to known more about the zoom levels used by the backend service you are consuming tiles from. Something like this from MapBox:

Screenshot 2021-05-13 at 16 36 45

@1075dn
Copy link

1075dn commented May 13, 2021

@ibrierley I think only plugins can access the map state (which contains functions like getPixelBounds, getBoundsCenterZoom etc.). Any advice?

@kengu Thanks for the tip - if I can't get something working with the existing functions, I'll try that...

@1075dn
Copy link

1075dn commented May 15, 2021

Is there any way for a MapController to access the map state?

@github-actions
Copy link

github-actions bot commented Jul 9, 2021

This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days.

@github-actions github-actions bot added the Stale label Jul 9, 2021
@github-actions
Copy link

This issue was closed because it has been stalled for 5 days with no activity.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants