How to subclass Polygon in Leaflet? - leaflet

I am trying to create a Hex class for Leaflet where the center and side length should be in screen units (pixels) as opposed to lat/lng to obtain an effect like this over the map.
Code is here:
L.Hex = L.Polygon.extend({
initialize: function (args) {
console.log('arguments', JSON.stringify(arguments));
console.log('arguments[0]', JSON.stringify(arguments[0]));
// options = options || {};
this.center = args[0];
this.size = args[1];
this.options = args[2] || {};
L.Polygon.prototype.initialize.call(this, [], this.options);
},
points: function(center, size, map){
var latlngs = [0, 1, 2, 3, 4, 5, 6]
.map(p => this.hexCorner(center, size, p))
.map(p => map.layerPointToLatLng(p));
return latlngs;
},
hexCorner: function(center, size, i){
var angle_deg = 60 * i - 30;
var angle_rad = Math.PI / 180 * angle_deg;
return [center[0] + size * Math.cos(angle_rad),
center[1] + size * Math.sin(angle_rad)]
},
})
L.hex = function(){ return new L.Hex(arguments) }
Since the coordinates are in user space I think can only calculate them after adding to the map - but that's what fails. If add the points like this:
var h = L.hex([100, 100], 30);
var points = h.points([100, 100], 30, e.map);
h.setLatLngs(points);
h.addTo(map);
things work ok but my best attempt at onAdd which is this:
onAdd: function (map) {
var points = this.points(this.center, this.size, map);
this.setLatLngs(points);
map.addLayer(this);
},
with message TypeError: t._path is undefined
So the question is: where is the problem and how should I do it otherwise?

Change this.setLatLngs(points) to this._setLatLngs(points);.
With setLatLngs() the function this.redraw(); is called and this needs the map variable, which is applied with map.addLayer(this)

Related

Getting the map bounds of a GeoJson region in leaflet prior to zooming

I'm using leaflet and I'm loading up the regions of my map dynamically from a database, based on the bounding box coordinates of the currently viewed map. As I zoom in, the detail of each new layer increases. The same regions will exist in every layer of the map, and the same region on a different zoom level will have the same id.
I am currently attempting to calculate the target map bounds and target zoom level, so that I can load up all the intersecting regions within the new map bounding box. I currently have the following code.
zoomToFeature(e) {
//e.g. map zoom for currently visible map is 3
const layer = e.target;
let padding = [5, 5];
let layerBounds = layer.getBounds();
//e.g layerBounds returns:
//ne = lat: -37.770025, lng: 145.02439
//sw = lat: -37.834451, lng: 144.900952
let targetZoom = this.map.getBoundsZoom(layerBounds, false, padding);
targetZoom = Math.min(this.map.getMaxZoom(), targetZoom);
//e.g.targetZoom for this feature is 12
let center = layer.getCenter()
//e.g. center for this layer is lat: -37.78808138412046, lng: 144.93164062500003
let targetPixelBounds = this.map.getPixelBounds(center, targetZoom);
//e.g. targetPixelBounds: max: Point{x: 946751, y: 643578} min:{x:946114,y:643078}
//this looks very wrong, and so causes everything below to fail I think.
//am I supposed to reset the origin? am I meant to project the center and targetZoom?
let sw = this.map.unproject(targetPixelBounds.getBottomLeft());
let ne = this.map.unproject(targetPixelBounds.getTopRight());
let targetMapBounds = new L.LatLngBounds(sw, ne);
this.map.flyTo(center,targetZoom);
this.loadMapData(targetMapBounds, targetZoom).subscribe(() => {
this.removeOldRegions(); // deletes existing geojson
this.loadRegions(); // adds retrieved data to new geojson layer
//find the same region but in the new zoom layer
let newLayer = this.getLayerById(layer.feature.properties.id);
this.highlightFeature(newLayer);
});
}
It is going wrong at the
let targetPixelBounds = this.map.getPixelBounds(center, targetZoom)
line.
Any idea how I can fix this?
Ok, I've cracked the case.
The unproject lines in the original code above take zoom level as an argument, which I didn't put in the original code.. So I've refactored the code, which I have posted below.
Basically once I have the map bounding box, I can perform the fly to, and execute code to retrieve the new geojson at the new zoom level. By the time the animation has completed, the new geojson layer is already loaded. (Written in TypeScript. Sorry non TypeScript people)
zoomToFeature(e) {
const layer = e.target;
let padding = [5, 5];
let layerBounds = layer.getBounds();
let targetMapBoundsZoom = this.getTargetMapBoundsZoom(layerBounds, { padding: padding });
this.map.flyToBounds(targetMapBoundsZoom.bounds);
this.loadMapData(targetMapBoundsZoom.bounds, targetMapBoundsZoom.zoom).subscribe(() => {
this.removeOldRegions();
this.loadRegions();
//find the same region but in the new zoom layer
let newLayer = this.getLayerById(layer.feature.properties.id);
this.highlightFeature(newLayer);
});
}
getTargetMapBoundsZoom(bounds, options) {
let newBoundsCenterZoom = this._getBoundsCenterZoom(bounds, options);
let targetMapBoundsPixels = this.map.getPixelBounds(newBoundsCenterZoom.center, newBoundsCenterZoom.zoom);
let targetSw = this.map.unproject(targetMapBoundsPixels.getBottomLeft(), newBoundsCenterZoom.zoom);
let targetNe = this.map.unproject(targetMapBoundsPixels.getTopRight(), newBoundsCenterZoom.zoom);
let targetMapBounds = new L.LatLngBounds(targetSw, targetNe);
return {
bounds: targetMapBounds,
zoom: newBoundsCenterZoom.zoom
}
}
loadMapData(bounds, zoom): Observable<any[]> {
const boundingBox = Util.GetMapBounds(bounds);
const regionTypeId = this.regionTypeIds[this._regionType];
return this.mapService.getGeoJsonData("AU",
regionTypeId,
zoom,
boundingBox.n,
boundingBox.s,
boundingBox.e,
boundingBox.w).pipe(map((response: any) => {
this.mapGeoJsonData = this.createFeatureCollection(response.data);
return this.mapGeoJsonData;
}));
}
//this is a copy of the original _getBoundsCenterZoom that is internal to leaflet, with minor modifications.
_getBoundsCenterZoom(bounds, options) {
options = options || {};
bounds = bounds.getBounds ? bounds.getBounds() : L.latLngBounds(bounds);
var paddingTL = L.point(options.paddingTopLeft || options.padding || [0, 0]),
paddingBR = L.point(options.paddingBottomRight || options.padding || [0, 0]),
zoom = this.map.getBoundsZoom(bounds, false, paddingTL.add(paddingBR));
zoom = (typeof options.maxZoom === 'number') ? Math.min(options.maxZoom, zoom) : zoom;
if (zoom === Infinity) {
return {
center: bounds.getCenter(),
zoom: zoom
};
}
var paddingOffset = paddingBR.subtract(paddingTL).divideBy(2),
swPoint = this.map.project(bounds.getSouthWest(), zoom),
nePoint = this.map.project(bounds.getNorthEast(), zoom),
center = this.map.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom);
return {
center: center,
zoom: zoom
};
}
createFeatureCollection(data: any) {
let featureCollection = {
type: "FeatureCollection",
features: data.map(r => {
const geoJson = JSON.parse(r.geoJson);
if (geoJson.type === "GeometryCollection") {
geoJson.geometries = geoJson.geometries.filter(r => r.type === "Polygon" || r.type === "MultiPolygon");
}
let feature = {
type: "Feature",
id: r.id,
properties: { id: r.id },
geometry: geoJson
}
return feature;
})
};
return featureCollection;
}
The default behaviour for this is to read out the bounds and pass it to the map:
map.fitBounds(e.target.getBounds());
Then you function can look like:
zoomToFeature(e) {
let padding = [5, 5];
let layerBounds = e.target.getBounds();
let targetZoom = this.map.getBoundsZoom(layerBounds, false, padding);
targetZoom = Math.min(this.map.getMaxZoom(), targetZoom);
//e.g.targetZoom for this feature is 12
map.fitBounds(layerBounds , {padding: padding, maxZoom: targetZoom });
this.loadMapData(layerBounds , targetZoom).subscribe(() => {
this.removeOldRegions(); // deletes existing geojson
this.loadRegions(); // adds retrieved data to new geojson layer
//find the same region but in the new zoom layer
let newLayer = this.getLayerById(layer.feature.properties.id);
this.highlightFeature(newLayer);
});
}
this moves the map and zoom it to the layer.

Display a value when modifying a control using dat.GUI

Can anybody help me what I am showing in the image?
[1]: http://www.romualdorivera.com/three.js/dat.GUI_img_01.jpg
Here is my code:
var gui = new dat.GUI();
parameters = { x: 1, area: 1,}
gui.add(parameters, 'x', 1,400).name("Scale XY (in)").onChange();
gui.add(parameters, "area", value).name("Surface area=").onChange( x = x * 2);
If you have a plane on the XZ-plane (1 x 1):
var planeGeom = new THREE.PlaneGeometry(1, 1);
planeGeom.rotateX(-Math.PI / 2);
var plane = new THREE.Mesh(planeGeom, new THREE.MeshStandardMaterial({
color: "green"
}));
scene.add(plane);
then you can create an instance of dat.GUI and set its controllers like this:
parameters = {
x: 1,
area: 1,
}
var gui = new dat.GUI();
gui.add(parameters, 'x', 1, 400).name("Scale XY (in)").onChange(
function(value) {
plane.scale.set(value, 1, value);
parameters.area = value * value; // update the value of parameters.area
}
);
gui.add(parameters, "area", 1).name("Surface area=").listen(); // listen for updating of the value
It's based on the example of dat.GUI
jsfiddle example.

How to distinct two markers placed at the same coordinates in Nokia Here map

I have two markers placed at the same coordinates in a Nokia Here map.
The problem is I can access only at one marker. The other is below the first one.
Is there any options or something else to manage this case, in order to have access to all markers placed at the same coordinates ?
The only way to ensure that all markers are separately visible is to put the overlapping markers at slightly different locations. The best effect I have found is to use the clustering functionality down to zoom 15 and add a separate set of jittered markers to display at zoom 16+.
/**
* This is a H.clustering.ITheme which displays ordinary markers as
* noise points
*/
function SpiderifyTheme() {
var baseTheme = new H.clustering.DefaultTheme();
this.getClusterPresentation = function (cluster) {
var clusterIcon = baseTheme.getClusterPresentation(cluster).getIcon();
return new H.map.Marker(cluster.getPosition(), {
icon: clusterIcon,
min: cluster.getMinZoom(),
max: cluster.getMaxZoom()
});
};
this.getNoisePresentation = function (noisePoint) {
return new H.map.Marker(noisePoint.getPosition(), {
min: noisePoint.getMinZoom()
});
};
}
// dataPoints is an array of H.clustering.DataPoint elements
var len = dataPoints.length + 1;
var SCATTER = 0.0001; // When exploding a group this is the size of the ring.
var group = new H.map.Group();
var truncate = function(number, places) {
var shift = Math.pow(10, places);
return ((number * shift) | 0) / shift;
};
// Ensure that all markers are offset by a set amount.
dataPoints.forEach(function (dataPoint, index ) {
var jitteredPoint = {
lng: truncate(dataPoint.lng, 3) + SCATTER + (Math.cos(index) * SCATTER),
lat: truncate(dataPoint.lat, 3) + SCATTER + (Math.sin(index) * SCATTER)
},
marker = new H.map.Marker(jitteredPoint, {
min: 16}); // This needs to be one more than the max cluster level
group.addObject(marker);
});
var clusteredDataProvider = new H.clustering.Provider(dataPoints, {
clusteringOptions: {
eps: 32,
minWeight: 2
},
max : 15,
theme: new SpiderifyTheme()
});
// First add cluster for zooms 1-15
var clusteringLayer = new H.map.layer.ObjectLayer(clusteredDataProvider);
map.addLayer(clusteringLayer);
// Add the group for zooms 16+
map.addObject(group);
The result looks something like this - the first image shows clustered markers at low zoom. The second shows jittered markers at high zoom. You can alter the eps value of the clusterer to get other effects.

Add a vertical line marker to Google Visualization's LineChart that moves when mouse move?

Is it possible to display a vertical Line marker showing the current x-axis value on LineChart, and moving when mouse moves ?
Thanks in advance.
While this was difficult before, a recent update to the API makes it much easier! You need to use a mouseover event handler to get the mouse coordinates and the new ChartLayoutInterface to translate the coordinates into chart values. Here's some example code:
[edit - fixed cross-browser compatibility issue]
*[edit - updated to get the value of points near the annotation line]*
function drawChart() {
// Create and populate the data table.
var data = new google.visualization.DataTable();
data.addColumn('number', 'x');
// add an "annotation" role column to the domain column
data.addColumn({type: 'string', role: 'annotation'});
data.addColumn('number', 'y');
// add 100 rows of pseudorandom data
var y = 50;
for (var i = 0; i < 100; i++) {
y += Math.floor(Math.random() * 5) * Math.pow(-1, Math.floor(Math.random() * 2));
data.addRow([i, null, y]);
}
// add a blank row to the end of the DataTable to hold the annotations
data.addRow([null, null, null]);
// get the index of the row used for the annotations
var annotationRowIndex = data.getNumberOfRows() - 1;
var options = {
annotation: {
1: {
// set the style of the domain column annotations to "line"
style: 'line'
}
},
height: 400,
width: 600
};
// create the chart
var chart = new google.visualization.LineChart(document.getElementById('chart_div'));
// create 'ready' event listener to add mousemove event listener to the chart
var runOnce = google.visualization.events.addListener(chart, 'ready', function () {
google.visualization.events.removeListener(runOnce);
// create mousemove event listener in the chart's container
// I use jQuery, but you can use whatever works best for you
$('#chart_div').mousemove(function (e) {
var xPos = e.pageX - container.offsetLeft;
var yPos = e.pageY - container.offsetTop;
var cli = chart.getChartLayoutInterface();
var xBounds = cli.getBoundingBox('hAxis#0#gridline');
var yBounds = cli.getBoundingBox('vAxis#0#gridline');
// is the mouse inside the chart area?
if (
(xPos >= xBounds.left || xPos <= xBounds.left + xBounds.width) &&
(yPos >= yBounds.top || yPos <= yBounds.top + yBounds.height)
) {
// if so, draw the vertical line here
// get the x-axis value at these coordinates
var xVal = cli.getHAxisValue(xPos);
// set the x-axis value of the annotation
data.setValue(annotationRowIndex, 0, xVal);
// set the value to display on the line, this could be any value you want
data.setValue(annotationRowIndex, 1, xVal.toFixed(2));
// get the data value (if any) at the line
// truncating xVal to one decimal place,
// since it is unlikely to find an annotation like that aligns precisely with the data
var rows = data.getFilteredRows([{column: 0, value: parseFloat(xVal.toFixed(1))}]);
if (rows.length) {
var value = data.getValue(rows[0], 2);
// do something with value
}
// draw the chart with the new annotation
chart.draw(data, options);
}
});
});
// draw the chart
chart.draw(data, options);
}
See it working here: http://jsfiddle.net/asgallant/tVCv9/12

Setup projection on Leafletjs

Im using an Leafletjs for an home project(This is have it looks, right now.
But i can't find have to setup the projection, i have found it for OpenLayers, which looks like this:
// Openlayers settings
//var defaultMaxExtent = new OpenLayers.Bounds(427304, 6032920, 927142, 6485144);
var defaultMaxExtent = new OpenLayers.Bounds(427304, 6032920, 927142, 6485144);
var defaultProjection = "EPSG:25832";
var defaultUnits = "Meters";
var defaultResolutions = new Array(1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024);
var defaultExtent = new OpenLayers.Bounds(215446, 2103547, 706886, 6203897); //this extent is used when the page is loaded.
//var defaultExtent = new OpenLayers.Bounds(705446, 6203547, 706886, 6203897); //this extent is used when the page is loaded.
map = new OpenLayers.Map('map', { projection: defaultProjection, units: defaultUnits, maxExtent: defaultMaxExtent, resolutions: defaultResolutions, controls: [
// Hide controls by default
new OpenLayers.Control.Navigation({ wheelChange: HideInfoBox() }),
new OpenLayers.Control.ArgParser(),
new OpenLayers.Control.Attribution()]
});
layer = new OpenLayers.Layer.WMS("kort", "http://serverAddress?", { nbr: '', username: 'admin', password: 'adminadmin', layers: 'Overlayer', format: 'image/png' });
Anybody that can help me?
Update:
I have tried to take the standard projection from Leaflet and customized it, like so
L.CRS.EPSG25832 = L.extend({}, L.CRS, {
code: 'EPSG:25832',
projection: L.Projection.SphericalMercator,
transformation: new L.Transformation(0.5 / Math.PI, 0.5, -0.5 / Math.PI, 0.5),
project: function (latlng) { // (LatLng) -> Point
var projectedPoint = this.projection.project(latlng),
earthRadius = 6378137;
return projectedPoint.multiplyBy(earthRadius);
}
});
Now the projection is correct. But the problem now is the coordinates is wrong, so forexample if i get the coordinates from Leaflet, Kolding is now loacted in mid france not in Denmark.
I found the solution to the problem myself.
By doing this instead:
var crs = L.CRS.proj4js('EPSG:25832', '+proj=utm +zone=32 +ellps=GRS80 +units=m +no_defs', new L.Transformation(0.5 / (Math.PI * L.Projection.Mercator.R_MAJOR), 0.5, -0.5 / (Math.PI * L.Projection.Mercator.R_MINOR), 0.5));
var map = new L.Map('Krak-Map', { center: new L.LatLng(latitude, longitude), zoom: 17, crs: crs });