I have to draw a polygon on openlayers Map. This is my code:
draw = new Draw({
source: this.vectorSource,
type: 'Polygon'
})
draw.on('drawend', e => {
// sol 1, result is not as required
let coords = e.feature.getGeometry().getCoordinates()
//sol 2, give correct results, but drawn polygon gone
let coords = e..feature.getGeometry().transform('EPSG:3857', 'EPSG:4326').getCoordinates()
}
this.olmap.addInteraction(draw)
I have to store the transformed coordinates in DB, but solution #2 does not maintain the visibility of drawn poloygon.
In case of solution #1, it does not gives the required formated coordinates, if I try to transform them later using
transform(coords, 'EPSG:3857', 'EPSG:4326')
it does not return formated coordinates.
please guide me where i am wrong to maintain the visibility of polygon and get the transformed coordinates.
You need to clone the geometry
let coords = e..feature.getGeometry().clone().transform('EPSG:3857', 'EPSG:4326').getCoordinates();
otherwise you wil move the feature somewhere close to point [0, 0] in view cooordinates
Related
Maybe it is a stupid question but anyway,
On the search page in my application, I have a range input that determines search range (min 5 km, max 50 km.). Depending on set value, I'd like to change current zoom in MapBox GL map so that it displays only the area that has been set in this input (if search range is about 10 km., I should see only a map sector with a radius of 10 km.). I'm a bit weak at math, which prevents me to find this correlation.
Thanks in advance.
So you want the map area to encompass a circle of a size specified in kilometres.
One simple (but slightly bloaty) way to do this would be using Turf.
const rangeCircle) = turf.circle(map.getCenter().toArray(), radius, { units: 'kilometers' });
map.fitBounds(turf.bbox(rangeCircle))
That way you don't need to explicitly compute the zoom level - Mapbox's fitBounds() will figure that out for you.
Finally I figured out how to do that using Turf.js. Here is my solution:
function fitToRadius(radius) {
const { lng, lat } = mapbox.getCenter();
const center = [lng, lat];
const rangeCircle = turf.circle(
center,
radius,
{ units: "kilometers"}
);
const [coordinates] = rangeCircle.geometry.coordinates;
const bounds = new mapboxgl.LngLatBounds(
coordinates[0],
coordinates[0]
);
/* Extend the 'LngLatBounds'
to include every coordinate in the bounds result. */
coordinates.forEach((coord) => bounds.extend(coord));
mapbox.fitBounds(bounds);
}
Thanks to Steve Bennett for his hint about Turf.js.
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 have a situation where I'm using Cesium.js entities to draw bearing lines from a point. In addition, I'd like to have a label attached to these bearing lines on the entity to show what it is. This is easy enough to do by attaching a label to the polyline entity. So far what I'm doing is creating a "really long" line along the bearing from the reference point and using the midpoint along that line as an anchor for the label (in Typescript):
let newEntity : Cesium.Entity;
let gccalc : gc.GreatCircle = new gc.GreatCircle();
let bearing : number = 45.0; //Bearing for the line
//this.currentPos is the lat/lon for the reference point for our bearing line
//gccalc is a simple class for computing great circle lines and has been omitted here (it is not relevant to the problem)
let destination = gccalc.destination(this.currentPos[0], this.currentPos[1], bearing, 1500, 'MI');
let anchorLabel = gccalc.destination(this.currentPos[0], this.currentPos[1], bearing, 50, 'MI');
const lineMat = new Cesium.PolylineDashMaterialProperty({
color : this.typeColorMap.get(contact.type)
});
const poses = Cesium.Cartesian3.fromDegreesArrayHeights([this.currentPos[1], this.currentPos[0], 500,
destination[1], destination[0], 500]); //500 is an arbitrary altitude for aesthetics
const nameString : string = "StringLabel";
let lineLabel = {
text: nameString,
font: '16px Helvetica',
fillColor : this.typeColorMap.get(contact.type),
outlineColor : Cesium.Color.BLACK,
outlineWidth : 2,
verticalOrigin : Cesium.VerticalOrigin.MIDDLE,
horizontalOrigin : Cesium.HorizontalOrigin.MIDDLE,
pixelOffset : new Cesium.Cartesian2(20, 0),
//pixelOffsetScaleByDistance : new Cesium.NearFarScalar(1.5e-1, 30.0, 1.5e7, 0.5)
};
let bearingLine = new Cesium.PolylineGraphics();
bearingLine.positions = poses;
bearingLine.width = 4;
bearingLine.material = lineMat;
bearingLine.arcType = Cesium.ArcType.NONE;
const lineEntity = {
name : 'Bearing Line',
polyline : bearingLine,
position : Cesium.Cartesian3.fromDegrees(anchorLabel[1], anchorLabel[0]),
show : true,
label : new Cesium.LabelGraphics(
lineLabel,
) as Cesium.LabelGraphics,
};
newEntity = new Cesium.Entity(lineEntity);
But the problem is the label is in geodesic (lat/lon) coordinates and does not stay on the screen as the user zooms in and out on the bearing line. So I also attempted to scale the position using the pixelOffsetScaleByDistance property, but this also doesn't work very well, and doesn't keep the label near the line
under 3d rotations (because the X and Y scaling would technically have to change).
It seems what I really need are the screen-space positions of the line endpoints, and a way to create the entity label at that midpoint. Is there a way to do this? If not, what is the best way to ensure that my label is always near my polyline regardless of user interactions with the Cesium map (such as zooms and rotations)?
To give an idea of what I'm trying to do, here's a screencap of Cesium with the labels as implemented. They look correct here, but only because I've made sure the zoom level and rotation is correct:
For canvas layers, how can I access the clicked pixel of a specific tile? Given a LatLng like { lat: 37.68816, lng: -119.76196 }, how can I: #1, retrieve the correct tile clicked, and #2, the pixel coordinates within the tile? Both of these should consider maxNativeZoom.
A CRS like L.CRS.EPSG3857 is required. The map's CRS is accessible by map.options.crs. The true zoom, tile size (like 256, but could be 512 or higher about maxNativeZoom) and a pixel origin like map.getPixelOrigin() are required:
const latlngToTilePixel = (latlng, crs, zoom, tileSize, pixelOrigin) => {
const layerPoint = crs.latLngToPoint(latlng, zoom).floor()
const tile = layerPoint.divideBy(tileSize).floor()
const tileCorner = tile.multiplyBy(tileSize).subtract(pixelOrigin)
const tilePixel = layerPoint.subtract(pixelOrigin).subtract(tileCorner)
return [tile, tilePixel]
}
First, convert the latlng to a layer point. Now all units are in pixels.
Second, divide by tileSize and round down. This gives the tile "slippy" coordinates.
Third, multiply back by tileSize to get the pixel coordinates of the tile corner, adjusted for pixelOrigin.
Finally, to get the tile pixels, subtract the layer point from origin and tile corner.
I would like to know how to calculate the centre of a polygon created with this code from Mapbox: https://www.mapbox.com/mapbox.js/example/v1.0.0/show-polygon-area/
I would like to place a marker on the centre of the polygon after it's been created.
Thanks in advance.
To calculate the center of a polygon you first need to get it's bounds, that can be done using the getBounds method of L.Polygon which it enherits from L.Polyline:
Returns the LatLngBounds of the polyline.
http://leafletjs.com/reference.html#polyline-getbounds
It returns a L.LatLngBounds object which has a getCenter method:
Returns the center point of the bounds
http://leafletjs.com/reference.html#latlngbounds-getcenter
It returns a L.LatLng object which you can use to create a L.Marker:
var polygon = new L.Polygon(coordinates).addTo(map);
var bounds = polygon.getBounds();
var center = bounds.getCenter();
var marker = new L.Marker(center).addTo(map);
Or you can shorthand it:
var polygon = new L.Polygon(coordinates).addTo(map);
var marker = new L.Marker(polygon.getBounds().getCenter()).addTo(map);
Using that in the Mapbox example would look something like this:
function showPolygonArea(e) {
featureGroup.clearLayers();
featureGroup.addLayer(e.layer);
// Here 'e.layer' holds the L.Polygon instance:
new L.Marker(e.layer.getBounds().getCenter()).addTo(featureGroup);
e.layer.bindPopup((LGeo.area(e.layer) / 1000000).toFixed(2) + ' km<sup>2</sup>');
e.layer.openPopup();
}
You can use turf library.turf.center(features) gives you a point feature at the absolute center point of all input features. where features in your case will be the polygon selected which you can get using mapboxDraw.getAll()