I am using OpenLayers and I want to get all values from Marker (ol.Feature).
You can see in docs it is possible to add any value to ol.Feature.
import Feature from 'ol/Feature';
import Polygon from 'ol/geom/Polygon';
import Point from 'ol/geom/Point';
var feature = new Feature({
geometry: new Polygon(polyCoords),
labelPoint: new Point(labelCoords),
name: 'My Polygon' // <--- CUSTOM VALUE
});
// get the polygon geometry
var poly = feature.getGeometry();
// Render the feature as a point using the coordinates from labelPoint
feature.setGeometryName('labelPoint');
// get the point geometry
var point = feature.getGeometry();
I have a click event on map and I want to get those values.
this.map.on('click', (args) => {
this.map.forEachFeatureAtPixel(args.pixel, (feature, layer) => {
// do something
console.log(feature.values_); // <---- SEEMS LIKE 'PRIVATE' prop
});
});
It looks like ol.Feature has no method for getting these values. Is there any 'nicer' solution than feature.values_ ?
You can get all properties using
feature.getProperties()
or if you just need one, you can do
feature.get('name')
Related
I have one set of coordinates from which I create 2 LineStrings
// global variables, I have previously defined map
let route = null;
let routeFeature = null;
let routeOverlay = null;
let routeOverlayFeatur = null;
My main LineString
function createMainRoute(coordinates) {
route = new LineString(coordinates);
routeFeature = new Feature({ geometry: route });
routeFeature.setStyle(styles.mainRoute);
const routeLayer = new VectorLayer({
source: new VectorSource({
features: [routeFeature],
}),
});
map.addLayer(routeLayer);
}
My second LineString with the exact coordinates
function createMainRouteOverlay(coordinates) {
routeOverlay = new LineString(coordinates);
routeOverlayFeature = new Feature({ geometry: routeOverlay });
routeOverlayFeature.setStyle(styles.routeOverlay);
const routeOverlayLayer = new VectorLayer({
source: new VectorSource({
features: [routeOverlayFeature],
}),
});
map.addLayer(routeOverlayLayer );
}
Now I have a function that draws these 2 lines, this function is called when I get initial data from server
function init(coordinates) {
createMainRoute(coordinates);
createMainRouteOverlay(coordinates);
}
Goal is that I want to modify coordinates on the overlay line like the coordinates, style and so on without loosing track of the main route, but they are at the start the same.
Now I have a function that will dynamically change coordinates set of the overlay line. This function is called when the external data is changed.
function readjust(newCoordinates) {
routeOverlayFeature.getGeomety().setCoordinates(newCoordinates)
}
The problem here is that when I call readjust(), nothing happens on the map, line stays the same with these new coordinates, but when I comment out createMainRoute() from the init function, meaning i only have one line and just call readjust(), line is updated with the new data. Does anyone knows what is the issue here? Is something like this not supported or do I need to set some property to allow this? I don't get why they are in conflict when they are 2 separated variables and only one is updated. Any help is appreciated
Your features are sharing the same geometry
routeOverlayFeature = new Feature({ geometry: route });
should be
routeOverlayFeature = new Feature({ geometry: routeOverlay });
I'm migrating from Google Maps API to Apple MapKit JS for the simple reason I have a developer account with them and they offer more free hits.
Anyway, actual examples of MapKit JS are a bit thin (or at least Google isn't finding them - draw what conspiracy theories you will), so although I've got the basics going of displaying an embeded map, I can't seem to do the next step which is route between two points (Apple's documentation also seems impenetrable as they don't show examples).
Here's my script for a basic map:
<script>
mapkit.init({
authorizationCallback: function(done) {
done('[MY-TOKEN]');
}
});
var MarkerAnnotation = mapkit.MarkerAnnotation
var myMarker = new mapkit.Coordinate(55.9496320, -3.1866360)
var myRegion = new mapkit.CoordinateRegion(
new mapkit.Coordinate(55.9496320, -3.1866360),
new mapkit.CoordinateSpan(0.003, 0.003)
);
var map = new mapkit.Map("map");
var myAnnotation = new MarkerAnnotation(myMarker, { color: "#9b6bcc", title: "theSpace On The Mile"});
map.showItems([myAnnotation]);
map.region = myRegion;
</script>
Now I want to:
• Show a walking route between two points
• Include waypoints on the route
Could someone show the code that would achieve this? Once I can see an example I know I'll get it ;-)
Ok, so I've found a solution to this so sharing it here for the benefit of others.
Let's start by saying Apple's MapKit JS doesn't appear to have a waypoints option as offered by Google Maps API - so the way around that is to create a map that stores the markers in an array and then routes from one to the next. The code stores the location of the last waypoint in a variable, and doesn't bother to draw a route to the last waypoint if this is the first one in the array (obviously).
<script>
// Initiallise MapKit - you'll need your own long-lived token for this
mapkit.init({
authorizationCallback: function(done) {
done('[MY-TOKEN]');
}
});
// Function to draw the route once MapKit has returned a response
function directionHandler(error, data) {
data["routes"].forEach(function(route, routeIdx) {
if (routeIdx !== 0) { return; }
overlays = [];
route['path'].forEach(function(path) {
// This styles the line drawn on the map
let overlayStyle = new mapkit.Style({
lineWidth: 3,
strokeColor: "#9b6bcc"
});
let overlay = new mapkit.PolylineOverlay(path, {
style: overlayStyle
});
overlays.push(overlay);
});
map.addOverlays(overlays);
});
}
// This asks MapKit for directions and when it gets a response sends it to directionHandler
function computeDirections(origin,destination) {
let directionsOptions = {
origin: origin,
destination: destination,
transportType: mapkit.Directions.Transport.Walking
};
directions.route(directionsOptions, directionHandler);
}
// This sets the initial region, but is overridden when all points have been potted to automatically set the bounds
var myRegion = new mapkit.CoordinateRegion(
new mapkit.Coordinate(55.9496320, -3.1866360),
new mapkit.CoordinateSpan(0.05, 0.05)
);
var map = new mapkit.Map("map");
map.region = myRegion;
var myAnnotations = [];
// lastWaypoint variable is 'unset' initially so the map doesn't try and find a route to the lastWaypoint for the first point of the route
var lastWaypoint = "unset";
var directions = new mapkit.Directions();
// Array of co-ordinates and label for marker
waypoints = [
{name:'Sofi’s Bar',lat:55.9746308,lon:-3.1722282},
{name:'TThe Roseleaf Cafe',lat:55.975992,lon:-3.173474},
{name:'Hemingway’s',lat:55.9763631,lon:-3.1706564},
{name:'Teuchter’s Landing',lat:55.9774693,lon:-3.1713826},
{name:'The King’s Wark',lat:55.9761425,lon:-3.1695419},
{name:'Malt and Hops',lat:55.975885,lon:-3.1698957},
{name:'The Carrier’s Quarters',lat:55.9760813,lon:-3.1685323},
{name:'Noble’s',lat:55.974905,lon:-3.16714},
{name:'The Fly Half',lat:55.9747906,lon:-3.1674496},
{name:'Port O’ Leith',lat:55.974596,lon:-3.167525}
];
// Loop through the array and create marker for each
waypoints.forEach(function(data) {
var myAnnotation = new mapkit.MarkerAnnotation(new mapkit.Coordinate(data['lat'],data['lon']), {
color: "#9b6bcc",
title: data['name']
});
myAnnotations.push(myAnnotation);
// As long as this isn't the first point on the route, draw a route back to the last point
if(lastWaypoint!="unset") {
computeDirections(lastWaypoint,new mapkit.Coordinate(data['lat'],data['lon']));
}
lastWaypoint = new mapkit.Coordinate(data['lat'],data['lon']);
});
map.showItems(myAnnotations);
</script>
This map is for a pub crawl around Leith, so the trasportType is 'Walking', but change that to 'Automobile' if you so wish.
With credit to Vasile whose MapKit JS Demo (https://github.com/vasile/mapkit-js-demo) helped me understand a lot more about the options.
I have a web application, where a user can switch between some 160-ish layers. Most of them are Feature Layers, but some are of type ArcGISDynamicMapServiceLayer.
I need to be able to query those layers the same as I do with FeatureLayers: by clicking on any point on the map and displaying an infowindow.
This is my code so far (removed some bits for clarity):
executeQueryTask: function(evt, scope) {
//"this" is the map object in this context, so we pass in the scope from the caller,
//which will enable us to call the layer and map object and all the other precious widget properties
scope.map.graphics.clear();
scope.map.infoWindow.hide();
//we create a new Circle and set its center at the mappoint. The radius will be 20 meters
//default unit is meters.
var circle = new Circle({
/*...*/
});
// draw the circle to the map:
var circleGraphic = new Graphic(circle, /*...*/));
scope.map.graphics.add(circleGraphic);
var queryTask = new QueryTask(scope.layer.layer.url + "/" + scope.layer.layer.visibleLayers[0]);
var query = new Query();
query.returnGeometry = true;
query.outFields = ["*"];
query.geometry = circle.getExtent();
var infoTemplate = new InfoTemplate().setTitle("");
queryTask.execute(query, function(resultSet) {
array.forEach(resultSet.features, function(feature) {
var graphic = feature;
graphic.setSymbol(/*...*/));
//Set the infoTemplate.
// graphic.setInfoTemplate(infoTemplate);
//Add graphic to the map graphics layer.
scope.map.infoWindow.setContent(graphic.attributes);
scope.map.infoWindow.show(evt.mapPoint, scope.map.getInfoWindowAnchor(evt.screenPoint));
scope.map.graphics.add(graphic);
});
});
},
The key point is insise the queryTask.execute callback. If I uncomment and use graphic.setInfoTemplate(infoTemplate); the result is colored and upon a second click an infoWindow pops up.
There are 2 issues with this approach:
2 clicks are needed
I am unable to click on PolyLines and Points twice, so no infowindow pops up here.
This is why I added a circle to get a 100m buffer in radius to my click. Now I want to immediatly return an infoWindow.
At this point I'm struggeling to successfully create an Info Window, which is immediately displayed.
Currently the line scope.map.infoWindow.setContent(graphic.attributes); throws an error Failed to execute 'appendChild' on 'Node': parameter 1 is not of type 'Node'.
How can I create that Info Window?
I found a suitable approach, which leaves room for improvements. But this is for another iteration.
//create a new FeatureLayer object
var featureLayer = new FeatureLayer(scope.layer.layer.url + "/" + scope.layer.layer.visibleLayers[0], {
mode: FeatureLayer.MODE_SELECTION,
infoTemplate: new InfoTemplate("Attributes", "${*}"),
outFields: ["*"]
});
//we create a new Circle and set its center at the mappoint. The radius will be 20 meters
//default unit is meters.
var circle = new Circle({/*...*/});
// draw the circle to the map:
var circleGraphic = new Graphic(circle, /*...*/));
scope.map.graphics.add(circleGraphic);
var lQuery = new Query();
lQuery.returnGeometry = true;
lQuery.geometry = circle.getExtent();
featureLayer.queryFeatures(lQuery, function(results) {
array.forEach(results.features, function(feature) {
var graphic = feature;
graphic.setSymbol(/*...*/));
//now that we have the feature, we need to select it
var selectionQuery = new Query();
selectionQuery.geometry = feature.geometry;
featureLayer.selectFeatures(selectionQuery, FeatureLayer.SELECTION_NEW)
.then(function(selectedFeatures) {
console.info("selection complete", selectedFeatures);
if (!selectedFeatures.length) {
return;
}
scope.map.infoWindow.setFeatures(selectedFeatures);
scope.map.infoWindow.show(evt.mapPoint, "upperright");
});
});
});
The change here is, that we are no longer using a QueryTask, but create a new FeatureLayer object in selection mode, using the url and id of the visible layer.
The second noteworthy change is, that we no longer set the content of the infoWindow, but instead set selected features using infoWindow.setFeatures(selectedFeatures). Setting the content of an infoWindow, but not selecting features, hides the action list of the info window, this hinders you to zoom to an object or perform other custom operations.
In addition, this enables you( or me ) to view multiple results in the infoWindow, instead of just one.
I am working with leaflet api where user can put markers on map. I have made a custom button for putting marker.
I am willing to draw line between those markers i.e. using
L.polylines() but as I am new to javascript and leaflet I can't
understand how to pass those latlng point to array which will later be
used in these functions. For initial working I have passed static
coordinates(working as req).
L.easyButton('fa-link', function () {
var secureThisArea = [[-81, 100.75], [-76.50, 245.75], [-145.50, 184.25], [-128, 311.75]];
map.on('click', function fencePlace(e) {
L.marker([-81, 100.75], { icon: fenceIcon, draggable: true }).bindPopup("this is first").addTo(map);
L.marker([-76.50, 245.75], { icon: fenceIcon, draggable: true }).bindPopup("this is second").addTo(map);
L.marker([-145.50, 184.25], { icon: fenceIcon, draggable: true }).bindPopup("this is third").addTo(map);
L.marker([-128, 311.75], { icon: fenceIcon, draggable: true }).bindPopup("this is fourth").addTo(map);
L.polyline(secureThisArea).addTo(map);
});
}).addTo(map);
Adding another value to an array is easy, e.g.:
secureThisArea.push([-81, 100.75]);
You can find more details (also about anything else JavaScript related) at Mozilla Developer Network.
If you want to use the coordinates from the marker objects, you can get those with:
var myMarker = L.marker([-81, 100.75], { icon: fenceIcon, draggable: true }),
latLng = null;
latLng = myMarker.getLatLng();
Also take a look at the Leaflet documentation.
If i understand you correctly you want to create markers on click and connect them via polylines. That's easy to do, in code with comments to explain:
// Create new empty polyline and add it to the map
var polyline = new L.Polyline([]).addTo(map);
// Handle map click event
map.on('click', function(event) {
// New marker on coordinate, add it to the map
new L.Marker(event.latlng).addTo(map);
// Add coordinate to the polyline
polyline.addLatLng(event.latlng);
});
Now afterwards if you want to get all the coordinates added to the polyline you can use the getLatLngs method of L.Polyline which returns an array of L.LatLng objects.
Reference: http://leafletjs.com/reference.html#polyline
Example: http://plnkr.co/edit/h7aMwc?p=preview
I don't understand why this won't work with Coffeescript. I tried to simplify this example: http://openlayers.org/dev/examples/click-handler.html
In the view, I have a div with ID map and call initMap().
The error I got is:
TypeError: a.draw is not a function
In the middle of openlayers lib
I got the following code in a .js.coffeescript file:
#MarkOnce = OpenLayers.Class OpenLayers.Control,
defaultHandlerOptions:
'single': true
'double': false
'pixelTolerance': 0
'stopSingle': false
'stopDouble': false
initialize: ->
this.handlerOptions = OpenLayers.Util.extend({}, this.defaultHandlerOptions)
OpenLayers.Control.prototype.initialize.apply(this, arguments)
this.handler = new OpenLayers.Handler.Click(this, {'click': this.mark}, this.handlerOptions)
mark: (evt) ->
console.log 'mark'
alert('pan')
default_marker = (lonlat) ->
size = new OpenLayers.Size(21,25)
offset = new OpenLayers.Pixel(-(size.w/2), -size.h)
icon = new OpenLayers.Icon('http://www.openlayers.org/dev/img/marker.png',size,offset)
marker = new OpenLayers.Marker(lonlat,icon)
return marker
#initMap = (lon,lat,marker) ->
#map = load_map(lon,lat)
render_marker(#map,lon,lat) if marker
set_projection(#map,lon,lat)
set_callbacks(#map)
load_map = (lon,lat) ->
map = new OpenLayers.Map 'map'
layer = new OpenLayers.Layer.OSM()
map.addLayer(layer)
map
set_projection = (map, lon, lat) ->
projection = new OpenLayers.Projection("EPSG:4326")
point = new OpenLayers.LonLat(lon,lat)
console.log(point)
center = point.transform(projection, map.getProjectionObject())
map.setCenter(point, 5)
render_marker = (map,lon,lat) ->
layer_once = new OpenLayers.Layer.Markers("mark_once")
map.addLayer(layer_once)
lonlat = new OpenLayers.LonLat(lon,lat)
layer_once.addMarker(#default_marker(lonlat))
#numMarkers++
set_callbacks = (map) ->
click = new MarkOnce()
map.addControl(click)
click.activate()
EDIT: It seems that if you change the this.defaultHandlerOptions for {draw: -> alert 'pan'} it works. the question is now: What is happening in this scope? What is "this"?
EDIT2: One possible problem with this abordage is that whenever I zoom out or in in the map, the markers are placed in the center of the map. How to avoid this to happen?
EDIT3: If you get to this point, blow up whatever you are doing and go use leaflet .
I had the same problem with CoffeeScript creating a click-handler from OpenLayers.Control and OpenLayers.Handle.Click using OpenLayers.Class like you tried - but it seems thats not needed.
See Do I have a OpenLayers / jQuery conflict here?
for how to establish an eventlistener in the options parameter of the map's constructor very easily. That worked fine for me.