I have a simple fictional map that I want to control using Leaflet. It is a flat 2D plane and its "latitude/longitude"/coordinate system spans from [0,0] to [999,999].
I have customized the map as follows:
window.map = L.map('leaflet-map', {
crs: L.CRS.Simple,
center: [500, 500],
zoom: 13,
maxBounds: [
[0, 0],
[999, 999],
],
layers: [new MyLayer()],
});
To draw this map, I've created a new layer, MyLayer, which extends gridLayer:
export var MyLayer = GridLayer.extend({
createTile: function(coords, done) {
var error;
var xmlhttprequest = new XMLHttpRequest();
xmlhttprequest.addEventListener('readystatechange', function() {
done(error, dothething());
});
xmlhttprequest.open('GET', /* url */);
xmlhttprequest.send();
},
});
The problem I have is the URL accepts the [0,0] to [999,999] coordinate system as parameters but I can't find how to actually get those. I understand there may be some decimal element but I can floor that as appropriate.
When centered on [500, 500, 13] the coords object contains { x: 15516, y: -21558, z: 13 }. When passed to L.CRS.Simple.pointToLatLng(coords, coords.z) I get { lat: 2.631591796875, lng: 1.89404296875 }.
I've downloaded the source code in an attempt to understand how this transformation happens from Map._move(center, zoom, data) but all that appears to do is call this.options.crs.latLngToPoint(), which is exactly what I reverse in L.CRS.Simple.pointToLatLng. I'm frankly at a loss.
First of all, I encourage you to read the Leaflet tutorial on L.CRS.Simple once again. Let me quote a relevant bit from there :
In a CRS.Simple, one horizontal map unit is mapped to one horizontal pixel, and idem with vertical. [...] we can set minZoom to values lower than zero:
So you have no reason to go down to zoom level 13 on your L.CRS.Simple map by default, really. For a [0,0]-[999,999] map, use zoom level zero for an overview, or use map.fitBounds([[0,0],[999,999]]).
The values that the createTile() method receives are tile coordinates, not CRS coordinates. A level-0 tile is split into four level-1 tiles, sixteen level-2 tiles, 64 level-3 tiles, and so on, up to 2^13 tiles at level 13. This is easier to visualize by playing with a L.GridLayer that displays the tile coordinates, like:
var grid = L.gridLayer({
attribution: 'Grid Layer',
// tileSize: L.point(100, 100),
});
grid.createTile = function (coords) {
var tile = L.DomUtil.create('div', 'tile-coords');
tile.innerHTML = [coords.x, coords.y, coords.z].join(', ');
return tile;
};
map.addLayer(grid);
Second: you want to use the internal _tileCoordsToBounds method, defined at L.GridLayer. Give it a set of tile coordinates, and you'll get back a L.LatLngBounds with the area covered by such a tile.
The following example (try it live here) should put you on track. Remember to read the documentation for L.LatLngBounds as well.
var grid = L.gridLayer({
attribution: 'Grid Layer',
// tileSize: L.point(100, 100),
});
grid.createTile = function (coords) {
var tile = L.DomUtil.create('div', 'tile-coords');
var tileBounds = this._tileCoordsToBounds(coords);
tile.innerHTML = [coords.x, coords.y, coords.z].join(', ') +
"<br>" + tileBounds.toBBoxString();
return tile;
};
map.addLayer(grid);
Related
For a project I need to convert latitude and longitude coordinates to the map layer (map html canvas) point coordinates (in x and y). I have gone through almost the whole of Mapbox's documentation, but I can't seem to find it. Does anybody know how to do it?
(Javascript)
This:
let point;
coordinates = [20,50]
point = convert(coordinates); // => point = (x, y);
You can use mapboxgl.Map method called "project". It returns mapboxgl.Point by LngLatLike coordinates.
TypeScript:
const coordinates: LngLatLike = [37, 55];
const point = map.project(coordinates); // return Point object
// output: {
// x: 713.802690844605
// y: 390.2335262644118
// }
Here is an example code from the official website.
You have to register from the official website. And then you should get an access token for using Mapbox in your project. So it is quite simple.
You can watch this video to understand visually how to do it.
<script>
mapboxgl.accessToken = '<your access token here>';
var map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/streets-v11',
center: [12.550343, 55.665957],
zoom: 8
});
var marker = new mapboxgl.Marker()
.setLngLat([12.550343, 55.665957])
.addTo(map);
</script>
Here you see above, in the "center" attribute you can define your own lat(firstly) and lng(secondly)
I am using Leaflet with Proj4Leaflet to work with tiles in 25832. The application is fairly simple: I am trying to overlay tiles in EPSG:25832 onto a omniscale basemap. I have copied the individual resolutions and origin from the tilemap meta information. The problem I am facing is that the map is not aligned and once I zoom in the tiles are not placed in the correct order. I'd appreciate any kind of support here (by the way, this is a working example which is using openlayers).
I guess I am doing something wrong here:
// Set resolutions
var resolutions = [156367.7919628329,78183.89598141646,39091.94799070823,19545.973995354114,9772.986997677057,4886.4934988385285,2443.2467494192642,1221.6233747096321,610.8116873548161,305.40584367740803,152.70292183870401,76.35146091935201,38.175730459676004,19.087865229838002,9.543932614919001,4.7719663074595005,2.3859831537297502,1.1929915768648751];
// Define CRS
var rs25832 = new L.Proj.CRS(
'EPSG:25832',
proj4rs25832def,
{
origin: [ 273211.2532533697, 6111822.37943825 ],
resolutions: resolutions
}
);
...using the tiles information from https://mapproxy.bba.atenekom.eu/tms/1.0.0/privat_alle_50_mbit/germany .
Afterwards I add a tile layer
var url = 'https://mapproxy.bba.atenekom.eu/tms/1.0.0/privat_alle_50_mbit/germany/{z}/{x}/{y}.png';
var tileLayer = L.tileLayer(
url,
{
tms: true,
crs: rs25832,
continuousWorld: true,
maxZoom: resolutions.length
}
);
And add them to the map..
// Setup map
var map = L.map('map', {
crs: rs25832,
center: [ 50.8805, 7.3389 ],
zoom:5,
maxZoom: resolutions.length,
layers: [ baseWms, tileLayer ]
});
The bare minimum of code can be found here: https://jsfiddle.net/6gcam7w5/8/
This boils down to how the Y coordinate of TMS tiles is inverted (it becomes higher when going north, as opposed to default TileLayers, in which the Y coordinate becomes larger when going south).
Having a look on the Leaflet code that takes care of this specific feature will shed some light on the issue:
if (this._map && !this._map.options.crs.infinite) {
var invertedY = this._globalTileRange.max.y - coords.y;
if (this.options.tms) {
data['y'] = invertedY;
}
data['-y'] = invertedY;
}
There are two things critical to calculating the right Y coordinate for your tiles here:
The CRS must be finite (it must have bounds)
There must be a finite global tile range (which in Leaflet is ultimately defined by the CRS bounds and not the TileLayer bounds)
Long story short, your CRS should be defined with known bounds. For this particular case, taking information from the TMS capabilities document...
<BoundingBox minx="273211.2532533697" miny="5200000.0" maxx="961083.6232988155" maxy="6111822.37943825"/>
...and turned into a L.Bounds definition when defining the Leaflet CRS, like...
// Define CRS
var rs25832 = new L.Proj.CRS(
'EPSG:25832',
proj4rs25832def,
{
origin: [ 273211.2532533697, 6111822.37943825 ],
resolutions: resolutions,
bounds: [[273211.2532533697, 5200000],[961083.6232988155, 6111822.37943825]]
}
);
Stuff should just work (with no need to pass the CRS to the tilelayers, since they will all use the map's), as in this working example.
I'm using LeafletJS to make a custom lab layout map and I plan to put in some rectangle layers to show whether or not areas are in use. Currently I have a working test case using coordinates to define each shape, but is there a way I can create a standard sized shape object which can be called and fed a single coordinate to center itself on?
Here is the current code from my Angular controller if it helps.
function showMap() {
var map = L.map('mapid', {
crs: L.CRS.Simple,
maxZoom: 4,
attributionControl: false
}).setView([0, 0], 1),
southWest = map.unproject([0, 4096], map.getMaxZoom()),
northEast = map.unproject([4096, 0], map.getMaxZoom()),
bounds = L.latLngBounds(southWest, northEast);
L.tileLayer('images/4231/{z}/{x}/{y}.png', {
minZoom: 1,
maxZoom: 4,
center: [0, 0],
noWrap: true,
bounds: bounds
}).addTo(map);
var testBench = [{
number: "1A1",
coord1: "-48.6",
coord2: "6",
coord3: "-81.4",
coord4: "71",
inUse: true
}, {
number: "1A2",
coord1: "-48.5",
coord2: "71",
coord3: "-81",
coord4: "137",
inUse: false
}, {
number: "1A3",
coord1: "-48.5",
coord2: "137",
coord3: "-81",
coord4: "202",
inUse: true
}];
angular.forEach(testBench, function(item, index) {
var location = [
[item.coord1, item.coord2],
[item.coord3, item.coord4]
],
color;
switch (item.inUse) {
case true:
color = "red"
break;
case false:
color = "green"
break;
}
L.rectangle(location, {
color: color,
weight: 1
}).bindPopup("Bench Number is: " + item.number).addTo(map);
})
map.setMaxBounds(bounds);
}
Eventually the bench info will be pulled from a DB rather than a variable and there will be hundreds of benches, so I'm looking to streamline the positioning layout as much as possible.
is there a way I can create a standard sized shape object which can be called and fed a single coordinate to center itself on?
In Leaflet, no.
Leaflet vector features, or L.Paths (L.Polylines and L.Polygons) are defined by their coordinates, not by their centroid and a series of offsets to that centroid.
You might want to implement a simple Factory design pattern to create regularly-shaped features giving only a center point, though.
I've been using openlayers 3, 3.17.1, to draw some custom tiles and I noticed that the y coordinate passed to my ol.source.UrlTile subclass' getTile method are negative. Why?
edit: here's how to see what I'm seeing
var MyVectorTile = function (options) {
ol.source.VectorTile.call(this, options);
this.customOption_ = options.customOption;
};
ol.inherits(MyVectorTile, ol.source.VectorTile);
MyVectorTile.prototype.getTile = function (z, x, y, pixelRatio, proj) {
console.log(z, x, y);
return ol.source.VectorTile.prototype.getTile.call(this, z, x, y, pixelRatio, proj);
}
here's another way to see it - add this layer to your map
new ol.layer.Tile({
visible: true,
preload: 16,
source: new ol.source.TileDebug({
projection: "EPSG:3857",
tileGrid: new ol.tilegrid.createXYZ({
maxZoom: 22
}),
color: 'rgba(255,204,0,1)'
}),
title: 'Ol3 Tile Debug'
});
My best guess, after going through ol3 with the JavaScript debugger, is that these are internal coordinates and that ol.source.TileDebug will show them unchanged.
In the function ol.TileUrlFunction.createFromTemplate there is this part.
var y = -tileCoord[2] - 1;
From what I can tell this changes the negative y coordinate into a positive one and inserts it into the {y} placeholder in a tile URL (they call URLs with placeholders "template"), I tried with OpenStreetMap tiles and the input and output match.
ol.source.TileDebug however does not go through this function (there is no "template" URL, and even if you specify one the function is not called) and will just print those internal coordinates on screen.
I am working on a leaflet project where I need the coordinates 0 to 4096 and which the leaflet coordinates that I need are the following:
0,0 bottom left (southWest)
0,4096' top left (northWest)
4096',4096' top right (northEast)
4096',0 bottom right (southEast)
I have tried many things to get the coordinates to map as needed but no luck. Here is my jsfiddle with my example, which has a console.log when you click on the map which shows the coordinates. If you click in the top left you will see its 0,0 and if you click in the bottom right you will see 4096,4096 which is backwards from whats needed. Here is my code that I have
url = 'http://{s}.tile.osm.org/{z}/{x}/{y}.png';
weight = 4096;
height = 4096;
mapMinZoom = 1;
mapMaxZoom = 7;
map = L.map('map', {
maxZoom: mapMaxZoom,
minZoom: mapMinZoom,
noWrap: true,
detectRetina: true
});
unproject = function(coord) {
return map.unproject(coord, 4);
};
southWest = unproject([0, 0]);
northEast = unproject([height, weight]);
map.setMaxBounds(new L.LatLngBounds(southWest, northEast));
tileLayer = L.tileLayer(url, {
minZoom: mapMinZoom,
maxZoom: mapMaxZoom,
noWrap: true,
tileSize: 256,
detectRetina: false
}).addTo(map);
map.setView(unproject([0, 0]), 2);
map.on('click', function(e) {
var coords, latLong;
latLong = new L.LatLng(e.latlng.lat, e.latlng.lng);
coords = map.project(latLong, 4);
console.log("x="+coords.x+", y="+coords.y);
});
If I understand correctly, you would like a map coordinates like this:
[0, 4096] .. [4096, 4096]
.........................
.........................
[0, 0] ........ [4096, 0]
It is good as vertical coordinate increases while going up (like latitude on Leaflet) and horizontal coordinate increases while going right (like longitude on Leaflet). So you do not seem to need to invert the vertical axis.
As for the order of the coordinates, unfortunately Leaflet uses latitude first, longitude second, and it is almost impossible to switch them natively. However, you can easily build a wrapper method that just swaps the coordinates. (similar to Leaflet LatLngBounds with simpler CRS & projection, but no need to use negative vertical coordinate -y since in your case you want it in the same direction as latitude)
Then another question would be how you want the coordinates in-between those 4 corners. The default CRS (Web Mercator) is not linear along the vertical axis. If you need your coordinates to be linear, you should use L.CRS.Simple. See Leaflet tutorial for non-Earth-based maps.