Closed nbarrett closed 7 months ago
Hi @nbarrett. I haven't done much in the the way of development with react-leaflet... however (looking at the problematic tile request) it would appear that the issue relates to the following:
As you have suggested there shouldn't be any reason why a react-leaflet app cannot utilise the Proj4JS and proj4Leaflet libraries. I therefore think the problem might be coming down to the map
not being destroyed; and reinitiated with the appropriate options (i.e. a custom CRS) when you switch to the EPSG:27700 base mapping.
Thanks @tmnnrs for your response. There were a few typos in my Layer data and I was getting the layer name slightly wrong as in the OS Maps API Demo, it's encoded into Leisure%2027700
which does work. I do have some zoom constraints in my app that ensure that the UI zoom levels are kept within limits e.g.
tileMatrixSet: "EPSG:27700",
minZoom: 0,
maxZoom: 13,
tileMatrixSet: "EPSG:3857",
minZoom: 7,
maxZoom: 20,
In order to better understand the behaviour differences, I've done a like-for-like comparison of querystring parameters between the working OS Maps API Demo and my from My App via react-leaflet and it seems that the thing that's wrong is the setting of the tileCol, tileMatrix and tileRow which are all wildly out.
From OS Maps API Demo
https://api.os.uk/maps/raster/v1/wmts? key=(mykey) layer=Leisure%2027700 request=GetTile service=WMTS style=default tileCol=213 tileMatrix=6 tileMatrixSet=EPSG:27700 tileRow=333 version=1.0.0
From My App via react-leaflet
https://api.os.uk/maps/raster/v1/wmts?key= key=(mykey) layer=Leisure%2027700& request=GetTile service=WMTS style=default tileCol=32749 tileMatrix=16 tileMatrixSet=EPSG:27700& tileRow=21792 version=1.0.0
The thing that confuses me is that with react-leaflet you supply a url with placeholders on the end e.g. &tileMatrix={z}&tileRow={y}&tileCol={x}
and based on the zoom you set in the UI, it's (presumably) supposed to calculate the parameters for you. But I don't know how I control this. Do you have any idea how the calculations should be done if I was to manually set those parameters? For instance when I manually hard-code the end of the url to &tileMatrix=6&tileRow=333&tileCol=213
it works and I get a map of Westminster, but I don't know how those values should be calculated automatically by react-leaflet.
Provided the CRS has been set within the map options - all the parameter calculations should be calculated automatically.
It feels like the CRS isn't getting set... meaning the map is defaulting to Web Mercator (EPSG:3857) hence the incorrect tileMatrix + tileRow + tileColumn values.
There is https://github.com/OrdnanceSurvey/tile-name-derivation to help with the mathematical relationships which can be used to derive tile names (ZXY and WMTS) from coordinates at a particular zoom level and vice-versa, however the Proj4JS and proj4Leaflet libraries should be taking away this headache.
Have you seen the OS Data Hub Examples? These are vanilla JS + Leaflet code examples, but they might offer some further insight into where the react-leaflet setup might be going wrong.
Thanks @tmnnrs - you got me on the right track!!
For anyone else looking for a solution to this (I've seen a few people asking about this on stack overflow and no responses exist on there), the answer was that I needed to add a custom CRS - I did this in the form of a custom hook useCustomCRSFor27700Projection()
which returns a crs
value which can be passed into the crs
prop of the MapContainer
. I've enclosed example code for this hook below which was inspired by the leaflet js + plain html example on the os-maps-api website:
import * as L from "leaflet";
import proj4 from "proj4";
export function useCustomCRSFor27700Projection() {
const zoom = 7 // could be some changeable app state
const center = [51.505, -0.09] // could be some changeable app state
const crs = new L.Proj.CRS("EPSG:27700", "+proj=tmerc +lat_0=49 +lon_0=-2 +k=0.9996012717 +x_0=400000 +y_0=-100000 +ellps=airy +towgs84=446.448,-125.157,542.06,0.15,0.247,0.842,-20.489 +units=m +no_defs", {
resolutions: [896.0, 448.0, 224.0, 112.0, 56.0, 28.0, 14.0, 7.0, 3.5, 1.75],
origin: [-238375.0, 1376256.0]
});
function transformCoords(input) {
return proj4("EPSG:27700", "EPSG:4326", input).reverse();
}
const options = {
crs,
minZoom: 0,
maxZoom: 8,
center,
zoom: zoom,
maxBounds: [
transformCoords([-238375.0, 0.0]),
transformCoords([900000.0, 1376256.0])
],
attributionControl: false
};
return {crs, options};
}
With that hook defined, you can then use it the vicinity of your MapContainer
usage:
const useCustomCRS = true // some boolean value that determines whether or not you need the custom crs (might change if you want to choose different projections)
const customCRS = useCustomCRSFor27700Projection();
const crs = useCustomCRS ? customCRS.crs : L.CRS.EPSG3857;
const layer = "Leisure_27700";
const key = "my-api-key";
return <div style={{height: '80vh', width: '100%'}}>
<MapContainer crs={crs}
minZoom={customCRS.options.minZoom}
maxZoom={customCRS.options.maxZoom}
zoom={customCRS.options.zoom} center={customCRS.options.center} scrollWheelZoom={true}
style={{height: '100%'}}>
<TileLayer url={`https://api.os.uk/maps/raster/v1/zxy/${layer}/{z}/{x}/{y}.png?key=${key}`}/>
</MapContainer>
</div>;
Example of app now working at: https://oscillation-production.up.railway.app/ - and commit that delivered the above
Footnote: Might be a good idea to provide some other examples in this repo (especially react-leaflet) or example projects built with more "current" tooling as I suspect the majority of developers are not building pure html solutions nowadays?
Background to Problem I'm developing a typescript react web application using the react-leaflet library. A more detailed tooling list is documented on the readme of my github project. I've already got a deployed version up and running that you can take a look at.
I initially built the app to use the OpenStreetMaps mapping provider as this was easier to get the auth setup, but the target application really needs to use the
Leisure_27700
Layer of OS Maps API. So for diagnostics, comparison and testing, I can now switch between different mapping providers in the UI.Everything's working quite well until I switch to the OS Maps
Leisure_27700
Layer, at which point the maps don't render. Under the covers, react-leaflet is issuing a series of get requests to the API e.g. https://api.os.uk/maps/raster/v1/wmts?key=mykeyremoved&service=WMTS&request=GetTile&version=1.0.0&style=default&layer=Leisure_027700&tileMatrixSet=EPSG:27700&tileMatrix=18&tileRow=87172&tileCol=131005.However on the specified 27700 Layer selections, I get HTTP 400 response from the OS Maps API with payload responses in the browser:
When I choose other Layers e.g. Light_3857 the application works okay. I've got an OSMaps developer premium account and the api key works in other OS-Data-Hub demo applications for instance: https://github.com/OrdnanceSurvey/OS-Data-Hub-API-Demos/tree/master/OSMapsAPI/Leaflet/Proj4Leaflet, so it does not seem to be a problem with me trying to access data I'm not entitled to.
The OS-Data-Hub-API-Demos demos do not use react-leaflet but use lower-level leaflet javascript for example here, so it's hard to see why these apps work but mine doesn't. Note that all parameters are passed by react-leaflet to the API using query string parameters and there is no http header setting. Could you let me know what I might be doing wrong please?
Steps to reproduce Please see my production deployment where you can choose between the different layers and see the behaviour outlined in this issue.
You can go into developer tools in Chrome and see the urls being attempted.
Thanks in advance for your help!