Having spent several hours looking through the docs, searching on here and looking at the "L" Object returned, I'm at a loss how to reference my leafletjs map for modifications, adding layers, markers, etc, throughout my code.
I get how the initial reference is destroyed to conserve memory, but how is it possible for me to reference my leaflet map and add/remove markers, layers, set styles, fit to bounds, etc after it's been created.
var layers=[];
var leafletMap = new L.map('mmp', { zoomControl: false, preferCanvas: true }).setView([51.505, -0.09], 13);
L.tileLayer('https://cartodb-basemaps-{s}.global.ssl.fastly.net/light_all/{z}/{x}/{y}.png', {
maxZoom: 18, attribution: '© OpenStreetMap, ©CARTO'
}).addTo(leafletMap);
var myGeojSonLayerGroup = L.geoJson(worldCountries.features, {
onEachFeature: myOnEachFeature,
style: myStyle
}).addTo(leafletMap)
function myOnEachFeature(feature, layer) {
var props = feature.properties;
var ni = {}
ni.name = props.name;
ni.layer = layer
leafletLayers.push(ni);
}
$('#mmp').data('layers',layers)
Using the above I can indeed colour various countries later on in my code, but I can't call leafletMap.fitBounds because leafletmap is no longer available, even though this was created as a global variable.
Is there any possible solution to this?
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 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);
I have a basic markerclusterer example which works very well.
var center = new google.maps.LatLng(37.4419, -122.1419);
var options = {
'zoom': 13,
'center': center,
'mapTypeId': google.maps.MapTypeId.ROADMAP
};
var map = new google.maps.Map(document.getElementById("map"), options);
var markers = [];
for (var i = 0; i < 100; i++) {
var latLng = new google.maps.LatLng(data.photos[i].latitude,
data.photos[i].longitude);
var marker = new google.maps.Marker({'position': latLng});
markers.push(marker);
}
var markerCluster = new MarkerClusterer(map, markers);
What I would like to do is cluster the markers by country and then once you click on it they are still clustered until on3 further click. Currently they are clustered until you are down to one result. I have thousands of markers and would like them visible after one country click and then one more click.
I looked for a solution online and found this http://google-maps-utility-library-v3.googlecode.com/svn/tags/markermanager/1.0/examples/google_northamerica_offices.html
which is produced using this
var officeLayer = [
{
"zoom": [0, 3],
"places": [
{ "name": "US Offices", "icon": ["us", "flag-shadow"], "posn": [40, -97] },
{ "name": "Canadian Offices", "icon": ["ca", "flag-shadow"], "posn": [58, -101] }
]
},
...
};
function setupOfficeMarkers() {
allmarkers.length = 0;
for (var i in officeLayer) {
if (officeLayer.hasOwnProperty(i)) {
var layer = officeLayer[i];
var markers = [];
for (var j in layer["places"]) {
if (layer["places"].hasOwnProperty(j)) {
var place = layer["places"][j];
var icon = getIcon(place["icon"]);
var title = place["name"];
var posn = new google.maps.LatLng(place["posn"][0], place["posn"][1]);
var marker = createMarker(posn, title, getIcon(place["icon"]));
markers.push(marker);
allmarkers.push(marker);
}
}
mgr.addMarkers(markers, layer["zoom"][0], layer["zoom"][1]);
}
}
mgr.refresh();
updateStatus(mgr.getMarkerCount(map.getZoom()));
}
I'm not sure how to implement this into what I've currently got and if i need to include any other scripts/ libraries also.
You are looking at two totally different libraries, there. Your question is about the MarkerClusterer library, but your example solution is about the MarkerManager library.
The MarkerClusterer library automatically clumps markers together based on an algorithm that tries to decide when too markers would be so close together that you can't visibly distinguish one from another. You don't really have a lot of control over when and how it decides to merge markers together this way, so this library is idea when it doesn't matter to you how they get merged, as long as merging happens. Since you want to merge markers together by political boundaries (countries) and not by proximity to each other, this is not the library for you.
The MarkerManager library does not automatically merge markers together at all. What it does do is to selectively hide and reveal markers based on the zoom level of the current map viewport. What you would need to do is do your own merging, and then add to the MarkerManager all of the merged markers, as well as the detail markers, and the zoom levels where you want each marker to be visible. Doing your own merging means you will need an alternate way of determining which country each marker point falls within. Hopefully, you already know (or can get) that information, because it's not automatically provided by any of these libraries.
tl;dr - use the MarkerManager library and not the MarkerClusterer library for grouping by countries, and it's up to you to identify the location for each country and which marker goes with which one.
I have a map with various markers and i need to be able to draw a rectangle on the map and select the markers which are within the rectangle bounds.
So far i have found some great info here: How to get markers inside an area selected by mouse drag?
I have implemented the keymapzoom plugin ok. like so
$('#dispatcher').gmap3({action:'get'}).enableKeyDragZoom({
boxStyle: {
border: "dashed black",
//backgroundColor: "red",
opacity: 0.5
},
paneStyle: {
backgroundColor: "gray",
opacity: 0.2
}
});
var dz = $('#dispatcher').gmap3({action:'get'}).getDragZoomObject();
google.maps.event.addListener(dz, 'dragend', function (bnds) {
alert(bnds);
});
This gives me the following
((lat,long),(lat,long)) format from the alert(bnds);
I need to know how i can now check if any markers are within this?
I already have an object that is storing the markers for another reason. like:
markers[name] = {};
markers[name].lat = lati;
markers[name].lng = longi;
which might be useful?
I don't understand how to use the GLatLngBounds and containsLatLng(latlng:GLatLng) as suggested.
Your question is tagged with the v3 version of the Maps API, so I'll assume you are using that version (which you should as v2 is deprecated). Note that some classes and methods are named different than in your question.
Bounds are represented with the LatLngBounds class. You can perform the contains method on an instance of that class to determine if a point lies within those bounds.
If you have an object with all your markers, you can loop through them and check each marker, for example:
var bounds = new google.maps.LatLngBounds(sw, ne);
for (var a in markers) {
if (bounds.contains(new google.maps.LatLng(markers[a].lat, markers[a].lng)) {
// marker is within bounds
}
}
On a side note, I would store the LatLng object in the markers object when creating them. That way you don't have to create them wherever you need.
Box/Rectangle Draw Selection in Google Maps
This was my solution..
google.maps.event.addListener(dz, 'dragend', function(e) { //important listener
for(var i = 0; i < markers.length; i++){ // looping through my Markers Collection
if(e.contains(markers[i].position))
console.log("Marker"+ i +" - matched");
}
});