I am using this sample, which uses layer blending and effect on multiple layers to create a more artistic way of highlighting features selected by the user. When the application loads, the basemap is displayed over all other layers in the map. When a user clicks on a country, the country will be highlighted.
The light gray canvas vector tiles layer and a countries feature layer are added to a group layer and the destination-over blendMode is set on the group layer. With the destination-over blendMode, the background layer covers, or is placed over, the top layer. The content of the top layer is visible where both layers do not overlap. When the app loads, you see the modern antique vector tiles basemap on top of the groupLayer.
My question is, is there a way to do the same with Feature Access instead of using portal ID.
<html>
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="initial-scale=1,maximum-scale=1,user-scalable=no"
/>
<title>
Highlight feature with effects and blending | Sample | ArcGIS API for
JavaScript 4.21
</title>
<style>
html,
body,
#viewDiv {
padding: 0;
margin: 0;
height: 100%;
width: 100%;
}
#messageDiv {
padding-left: 10px;
padding-right: 10px;
}
</style>
<link
rel="stylesheet"
href="https://js.arcgis.com/4.21/esri/themes/light/main.css"
/>
<script src="https://js.arcgis.com/4.21/"></script>
<script>
require([
"esri/WebMap",
"esri/views/MapView",
"esri/layers/VectorTileLayer",
"esri/Graphic",
"esri/layers/FeatureLayer",
"esri/layers/GraphicsLayer",
"esri/layers/GroupLayer"
], (
WebMap,
MapView,
VectorTileLayer,
Graphic,
FeatureLayer,
GraphicsLayer,
GroupLayer
) => {
const map = new WebMap({
basemap: {
portalItem: {
id: "f35ef07c9ed24020aadd65c8a65d3754" // modern antique vector tiles
}
}
});
const vtLayer = new VectorTileLayer({
portalItem: {
id: "2afe5b807fa74006be6363fd243ffb30" // gray vector tiles canvas
}
});
const countries = new FeatureLayer({
portalItem: {
id: "53a1e68de7e4499cad77c80daba46a94" // boundaries of countries
}
});
// This group layer groups the gray canvas vector tiles and
// countries feature layer.
// With destination-over blendMode, the background layer covers
// the top layer. The top layer is put behind the destination layer.
// So when the app starts, the basemap layer will be shown over this layer
const groupLayer = new GroupLayer({
layers: [vtLayer, countries],
blendMode: "destination-over"
});
map.add(groupLayer);
const view = new MapView({
container: "viewDiv",
map: map,
zoom: 6,
center: [2, 46],
constraints: {
snapToZoom: false,
minScale: 147914381
}
});
let layerView, animation;
// countryGraphicsLayer is added to the view's basemap.
// It will contain black polygon covering the extent of the world
// the country graphic will also be added to this layer when user clicks a country.
// With destination-in blend mode, the contents of background layer is
// kept where it overlaps with top layer. Everything else is made transparent.
// In this case, the countryGraphicsLayer will be displayed underneath
// modern antique vector tiles basemap.
// The bloom effect will add a glow around the selected country.
const countryGraphicsLayer = new GraphicsLayer({
blendMode: "destination-in",
effect: "bloom(200%)"
});
map.loadAll().then(async () => {
addWorld();
map.basemap.baseLayers.getItemAt(1).blendMode = "multiply";
// add the buffer graphicslayer to the basemap
map.basemap.baseLayers.add(countryGraphicsLayer);
// get a reference ot the countries featurelayer's layerview
// layerview will be queried to get the intersecting country
// when user clicks on the map
layerView = await view.whenLayerView(countries);
});
view.ui.add("messageDiv", "top-right");
const symbol = {
type: "simple-fill",
color: "white",
outline: null
};
// listen to the view's click event
view.on("click", async (event) => {
// query the countries featurelayer for a country that intersects the point
// user clicked on
const {
features: [feature]
} = await layerView.queryFeatures({
geometry: view.toMap(event),
returnGeometry: true,
maxAllowableOffset: 10000,
outFields: ["*"]
});
countryGraphicsLayer.graphics.removeAll();
animation && animation.remove();
let world = addWorld();
// add the clicked country feature to the graphicslayer
if (feature) {
feature.symbol = symbol;
countryGraphicsLayer.graphics.add(feature);
// add a fade animation to show the highlight effect
// for the selected country
animation = fadeWorld(world);
// zoom to the highlighted country
view.goTo(
{
target: view.toMap(event),
extent: feature.geometry.extent.clone().expand(1.8)
},
{ duration: 1000 }
);
}
});
function addWorld(world) {
world = new Graphic({
geometry: {
type: "extent",
xmin: -180,
xmax: 180,
ymin: -90,
ymax: 90
},
symbol: {
type: "simple-fill",
color: "rgba(0, 0, 0, 1)",
outline: null
}
});
countryGraphicsLayer.graphics.add(world);
return world;
}
// add a fading animation when user clicks on a country
function fadeWorld(world) {
let timer;
// requestAnimationFrame method specifies "frame" function
// to perform an animation where the opacity of the world polygon graphic
// decreased by 0.1 until it is 0 or completely transparent
// then the animation is cancelled
function frame() {
const symbol = world.symbol.clone();
symbol.color.a = Math.max(0, symbol.color.a - 0.1);
world.symbol = symbol;
if (symbol.color.a > 0) {
timer = requestAnimationFrame(frame);
}
}
frame();
return {
remove() {
cancelAnimationFrame(timer);
}
};
}
});
</script>
</head>
<body>
<div id="viewDiv"></div>
<div id="messageDiv" class="esri-widget esri-heading">
<h4 class="esri-heading">Click on a country</h4>
</div>
</body>
</html>
Related
The objective
I'm trying to replicate the "singleselect-hover" feature in this example from the OpenLayers site.
The issue
When I tried to use this implementation, the hit detection was very poor with vtLayer.getFeatures(event.pixel). The documentation for the function states:
The hit detection algorithm used for this method is optimized for performance, but is less accurate than the one used in map.getFeaturesAtPixel()
Indeed, when I switched to map.getFeaturesAtPixel, the performance increased, but the features still does not work entirely as expected.
When I move my pointer over a vector boundary from the outside, it (usually) behaves as expected:
However, when I move to an adjacent boundary and then back, the feature no longer works:
My code:
proj4.defs(
'EPSG:6931',
'+proj=laea +lat_0=90 +lon_0=0 +x_0=0 +y_0=0 +ellps=WGS84 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs'
);
register(proj4);
const projection = get('EPSG:6931');
const osm = new OSM();
const map = new Map({
target: something,
layers: [osm],
view: new View({
projection: projection,
zoom: 5,
}),
})
// Vector Styles
const unselectedStyle = new Style({
stroke: new Stroke({
color: 'rgba(50,50,50,0.9)',
width: 1.2,
}),
});
const selectedStyle = new Style({
stroke: new Stroke({
color: 'white',
width: 2,
}),
fill: new Fill({
color: 'rgba(0,0,0,0.3)',
}),
});
const createVtLayer = () => {
const vtSource = new VectorTileSource({
tileGrid: new TileGrid({
extent: [
-9009964.761231285, -9009964.761231285, 9009964.761231285,
9009964.761231285,
],
tileSize: 256,
resolutions: [70390.34969711941],
}),
projection: projection,
format: new MVT({ idProperty: 'some_id' }),
url:
geoserverUrl +
'/gwc/service/tms/1.0.0/' +
mvtLayerName +
'#EPSG%3A' +
projection.getCode().split(':')[1] + // EPSG number of current projection
'#pbf/{z}/{x}/{-y}.pbf',
});
return new VectorTileLayer({
zIndex: 1,
source: vtSource,
style: unselectedStyle,
});
};
const vtLayer = createVtLayer();
// Local lookup for highlighted features
let selection = null;
const selectionLayer = new VectorTileLayer({
zIndex: 2,
source: vtLayer.getSource(),
style: feature => feature === selection && selectedStyle,
});
if (map && vtLayer && selectionLayer) {
// Add layers to map once loaded into state
map.addLayer(vtLayer);
map.addLayer(selectionLayer);
// Update styling of selectionLayer on mouse hover
map.on(['pointermove', 'click'], event => {
// Send vector metadata to parent component on mouse click
if (event.type === 'click' && selection) {
onFeatureSelect(selection);
}
map.forEachFeatureAtPixel(
event.pixel,
feature => {
selection = feature;
}, {
layerFilter: layer => layer === vtLayer,
hitTolerance: 1,
}
);
selectionLayer.changed()
});
}
What I've tried so far
I've tried adjusting the renderBuffer and renderMode parameters in the VectorTile layer, as well as adjusting the hitTolerance option in map.forEachFeatureAtPixel, and I haven't had any luck yet. When I logged the feature id from the feature parameter in forEachFeatureAtPixel, I noticed something strange--when the unexpected behavior occurs, after dragging the pointer over an adjacent boundary line, the selected variable rapidly switches between the two features and assigns the undesired one, until I touch the pointer to a non-adjacent boundary line. Modifying the hitTolerance only causes the selected feature to switch more frequently.
Theories and questions
I'm thinking that maybe my adjacent vectors are overlapping each others boundaries? Or maybe there is a problem with the way the vectors are loaded as MVTs?
Adding an invisible Fill() to the unselectedStyle allowed the layer to be hit-detected and solved my issue!
// Vector Styles
const unselectedStyle = new Style({
stroke: new Stroke({
color: 'rgba(50,50,50,0.9)',
width: 1.2,
}),
fill: new Fill({
color: 'rgba(0,0,0,0)',
}),
});
Thanks Mike!
Mapbox GL JS w/ w3w grid
Hi, I'm just playing around with the What3Words grid on Mapbox code from the tutorial. (https://developer.what3words.com/tutorial/displaying-the-what3words-grid-on-a-mapbox-map)
I'm trying to make the tiles from the grid interactive, kind of like in the w3w website (clickable, hover effect, getting data from them, etc), but the grid doesn't seem to work when the data source is loaded as a 'fill' layer on Mapbox, it only works as a 'line' layer type. Every single example I find online uses Polygons (or MultiPolygons) from a fill layer type, but I can't see nothing around with bounding boxes.
(Basically trying to achieve something like this, but with every tile instead of the states: https://docs.mapbox.com/mapbox-gl-js/example/hover-styles/)
I don't really know what's going on, why can't I add the source data as a fill layer? Is there a way to load the data as Polygons instead of bounding boxes?
Thanks.
Code (from the tutorial):
<html>
<head>
<script src="https://assets.what3words.com/sdk/v3.1/what3words.js?key=YOUR_API_KEY"></script>
<script src="https://api.tiles.mapbox.com/mapbox-gl-js/v0.53.0/mapbox-gl.js"></script>
<link href="https://api.tiles.mapbox.com/mapbox-gl-js/v0.53.0/mapbox-gl.css" rel="stylesheet" />
<style>
#map {
height: 100%;
}
html, body {
height: 100%;
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<div id="map"></div>
<script>
// Create the Mapbox
mapboxgl.accessToken = "YOUR_MAPBOX_TOKEN";
let map = new mapboxgl.Map({
container: "map", // container id
style: "mapbox://styles/mapbox/streets-v9", // stylesheet location
center: [-0.195499, 51.52086], // starting position [lng, lat]
zoom: 18 // starting zoom
});
map.addControl(new mapboxgl.NavigationControl());
</script>
<script>
function drawGrid() {
const zoom = map.getZoom();
const loadFeatures = zoom > 17;
if (loadFeatures) { // Zoom level is high enough
var ne = map.getBounds().getNorthEast();
var sw = map.getBounds().getSouthWest();
// Call the what3words Grid API to obtain the grid squares within the current visble bounding box
what3words.api
.gridSectionGeoJson({
southwest: {
lat: sw.lat, lng: sw.lng
},
northeast: {
lat: ne.lat, lng: ne.lng
}
}).then(function(data) {
// Get the grid source from the map (it won't exist initally)
var grid = map.getSource('grid');
if (grid === undefined) {
// Create a source of type 'geojson' which loads the GeoJSON returned from the what3words API
map.addSource('grid', {
type: 'geojson',
data: data
});
// Create a new layer, which loads data from the newly created data source
map.addLayer({
id: 'grid_layer',
type: "line",
source: 'grid',
layout: {
"line-join": "round",
"line-cap": "round"
},
paint: {
"line-color": '#777',
"line-width": .5
}
});
} else {
// The source and map layer already exist, so just update the source data to be the new
// GeoJSON returned from the what3words API
map.getSource('grid').setData(data);
}
}).catch(console.error);
}
// If we have reached the required zoom level, set the 'grid_layer' to be visible
var grid_layer = map.getLayer('grid_layer');
if (typeof grid_layer !== 'undefined') {
map.setLayoutProperty('grid_layer', 'visibility', loadFeatures ? 'visible' : 'none');
}
}
// When the map is either loaded or moved, check to see if the grid should be draw
// if the appropriate zoom level has been met, and if so draw it on.
map
.on('load', drawGrid)
.on('move', drawGrid);
</script>
</body>
</html>
I am using Azure Maps SDK in a Blazor Server app which uses JavaScript interop. The map works well and renders perfectly.
On initial load of the map component the popup displays correctly when clicking a pin on the map. However, when the data source changes via an API call datasource.importDataFromUrl(organisationDataFeed); it correctly brings back the filtered data, but when I click on one of the pins again the popup does not display. The click event handler (for the pin) is being called, but the popup does not open.
(function () {
var map, datasource, popup;
// Global export
window.azuremaps = {
initMap: function (organisationDataFeed) {
// Create an instance of the map control and set some options.
map = new atlas.Map('map', {
// center: [-97, 39],
// center: [18.424095, -33.925000],
center: [26.157981, -29.083937],
zoom: 4,
pitch: 50,
style: 'night',
view: 'Auto',
// Add your Azure Maps subscription key to the map SDK. Get an Azure Maps key at https://azure.com/maps
authOptions: {
authType: 'subscriptionKey',
subscriptionKey: ''
}
});
map.controls.add([
new atlas.control.ZoomControl(),
new atlas.control.CompassControl(),
new atlas.control.PitchControl(),
new atlas.control.StyleControl()
], {
position: "top-right"
});
// Wait until the map resources are ready.
map.events.add('ready', function () {
// Create a data source and add it to the map.
datasource = new atlas.source.DataSource(null, {
//Tell the data source to cluster point data.
cluster: true,
//The radius in pixels to cluster points together.
clusterRadius: 45,
// The maximium zoom level in which clustering occurs.
// If you zoom in more than this, all points are rendered as symbols.
clusterMaxZoom: 15
});
map.sources.add(datasource);
//Create a bubble layer for rendering clustered data points.
var clusterBubbleLayer = new atlas.layer.BubbleLayer(datasource, null, {
//Scale the size of the clustered bubble based on the number of points inthe cluster.
radius: [
'step',
['get', 'point_count'],
20, //Default of 20 pixel radius.
100, 30, //If point_count >= 100, radius is 30 pixels.
750, 40 //If point_count >= 750, radius is 40 pixels.
],
//Change the color of the cluster based on the value on the point_cluster property of the cluster.
color: [
'step',
['get', 'point_count'],
'rgba(0,255,0,0.8)', //Default to green.
100, 'rgba(255,255,0,0.8)', //If the point_count >= 100, color is yellow.
750, 'rgba(255,0,0,0.8)' //If the point_count >= 100, color is red.
],
strokeWidth: 0,
filter: ['has', 'point_count'] //Only rendered data points which have a point_count property, which clusters do.
});
//Add a click event to the layer so we can zoom in when a user clicks a cluster.
map.events.add('click', clusterBubbleLayer, clusterClicked);
//Add mouse events to change the mouse cursor when hovering over a cluster.
map.events.add('mouseenter', clusterBubbleLayer, function () {
map.getCanvasContainer().style.cursor = 'pointer';
});
map.events.add('mouseleave', clusterBubbleLayer, function () {
map.getCanvasContainer().style.cursor = 'grab';
});
// Create a layer to render the individual locations.
var individualSymbolLayer = new atlas.layer.SymbolLayer(datasource, null, {
filter: ['!', ['has', 'point_count']], //Filter out clustered points from this layer.
textOptions: {
textField: ['get', 'name'],
color: "#FFFFFF",
offset: [0, -2.2]
},
});
map.events.add('click', individualSymbolLayer, symbolClicked);
//Add the clusterBubbleLayer and two additional layers to the map.
map.layers.add([
clusterBubbleLayer,
// Create a symbol layer to render the count of locations in a cluster.
new atlas.layer.SymbolLayer(datasource, null, {
iconOptions: {
image: 'none' //Hide the icon image.
},
textOptions: {
textField: ['get', 'point_count_abbreviated'],
offset: [0, 0.4]
}
}),
individualSymbolLayer
]);
// Create a popup but leave it closed so we can update it and display it later.
popup = new atlas.Popup({
pixelOffset: [0, -18],
closeButton: true
});
// Retrieve a GeoJSON data set and add it to the data source.
datasource.importDataFromUrl(organisationDataFeed);
});
},
};
function clusterClicked(e) {
if (e && e.shapes && e.shapes.length > 0 && e.shapes[0].properties.cluster) {
// Get the clustered point from the event.
var cluster = e.shapes[0];
// Get the cluster expansion zoom level. This is the zoom level at which the cluster starts to break apart.
datasource.getClusterExpansionZoom(cluster.properties.cluster_id).then(function (zoom) {
//Update the map camera to be centered over the cluster.
map.setCamera({
center: cluster.geometry.coordinates,
zoom: zoom + 2,
type: 'ease',
duration: 200
});
});
}
}
function symbolClicked(e) {
// Make sure the event occured on a point feature.
var popupTemplate = '<div class="card border-success" style="visibility: visible"><div class="card-header" style="visibility: visible">{name}</div><div class="card-body text-success" style="visibility: visible"><h5 class="card-title" style="visibility: visible">{description}</h5><p class="card-text" style="visibility: visible">Contact: {contact}</p><p class="card-text">Web: {website}</p></div></div>';
if (e.shapes && e.shapes.length > 0) {
var content, coordinate;
// Check to see if the first value in the shapes array is a Point Shape.
if (e.shapes[0] instanceof atlas.Shape && e.shapes[0].getType() === 'Point') {
var properties = e.shapes[0].getProperties();
content = popupTemplate.replace(/{name}/g, properties.name).replace(/{description}/g, properties.description).replace(/{contact}/g, properties.contact).replace(/{website}/g, properties.website);
coordinate = e.shapes[0].getCoordinates();
}
else if (e.shapes[0].type === 'Feature' && e.shapes[0].geometry.type === 'Point') {
// Check to see if the feature is a cluster.
if (e.shapes[0].properties.cluster) {
content = '<div style="padding:10px;">Cluster of ' + e.shapes[0].properties.point_count + ' symbols</div>';
} else {
// Feature is likely from a VectorTileSource.
content = popupTemplate.replace(/{name}/g, properties.name).replace(/{description}/g, properties.description).replace(/{contact}/g, properties.contact).replace(/{website}/g, properties.website);
}
coordinate = e.shapes[0].geometry.coordinates;
}
if (content && coordinate) {
// Populate the popupTemplate with data from the clicked point feature.
console.log("JB content");
console.log(content);
console.log("JB coordinate");
console.log(coordinate);
popup.setOptions({
//Update the content of the popup.
content: content,
//Update the position of the popup with the symbols coordinate.
position: coordinate
});
console.log("JB: logging map variable");
console.log(map);
popup.open(map);
}
}
}
})();
content and coordinate are populated with values and evaluate to true, the options are correctly set, but just the last line: popup.open(map); does not work if the data source changes bringing back new data into the map. It works perfectly on initial load.
Any ideas what I could do to get this working? Thanks
A couple of things to try:
Double check the values of coordinate and content. The coordinates should be an array with [longitude,latitude] numbers (make sure they aren't string values of numbers). The content should either be a DOMElement (i.e. div), or a string. If it is anything else, it may not display anything.
Double check the "map" variable is a map the second time around. If the reference is lost for some reason, that function won't work.
It looks like you are create a new popup all the time. Often apps only want to display a single popup at a time. In that case it is more efficient to create a single popup and reuse it as shown in this example: https://azuremapscodesamples.azurewebsites.net/index.html?sample=Reusing%20Popup%20with%20Multiple%20Pins
in my program I have created polygons on a graphicsLayer from coordinates on my mapView. When I hover over a polygon, I want to highlight that polygon with orange color.
require([
"esri/Map",
"esri/views/MapView",
"esri/Graphic",
"esri/layers/GraphicsLayer",
"esri/widgets/Search",
"dojo/dom",
"esri/renderers/UniqueValueRenderer",
"esri/symbols/SimpleLineSymbol"
], function (Map, MapView, Graphic, GraphicsLayer, Search, dom, UniqueValueRenderer, SimpleLineSymbol) {
map = new Map({
basemap: "streets-navigation-vector"
});
var center = app.addresses[0].center;
view = new MapView({
container: "viewDiv",
map: map,
center: center, // longitude, latitude
zoom: 15,
highlightOptions: {
color: "orange"
}
});
var search = new Search({
view: view,
container: "search"
});
graphicsLayer = new GraphicsLayer();
map.add(graphicsLayer);
var i = 0;
app.addresses.forEach(function (address) {
var polygon = {
type: "polygon",
rings: address.polygon_rings
};
address.polygonGraphic = new Graphic({
geometry: polygon,
symbol: address.parking_zone
});
graphicsLayer.add(address.polygonGraphic);
i++;
});
view.on("click", function(event){
var screenPoint = {
x: event.x,
y: event.y
};
view.hitTest(screenPoint).then(function (response) {
if (response.results.length) {
var graphic = response.results.filter(function (result) {
// check if the graphic belongs to the layer of interest
return result.graphic.layer === graphicsLayer;
})[0].graphic;
// do something with the result graphic
ToggleSelectedAddress(parseInt(graphic.uid));
}
});
});
view
.when()
.then(function() {
return graphicsLayer.when();
})
.then(function(layer) {
// Set up an event handler for pointer-down (mobile)
// and pointer-move events (mouse)
// and retrieve the screen x, y coordinates
return view.whenLayerView(layer);
})
.then(function(layerView) {
view.on("pointer-move", eventHandler);
view.on("pointer-down", eventHandler);
function eventHandler(event) {
// the hitTest() checks to see if any graphics in the view
// intersect the x, y coordinates of the pointer
view.hitTest(event).then(getGraphics);
}
let highlight;
function getGraphics(response) {
// the topmost graphic from the hurricanesLayer
// and display select attribute values from the
// graphic to the user
if (response.results.length) {
const graphic = response.results.filter(function(result) {
if(result.graphic.layer === graphicsLayer){
layerView.highlight(result.graphic);
}
return result.graphic.layer === graphicsLayer;
})[0].graphic;
if(highlight) {
console.log("highlighted");
highlight.remove();
}
// highlight all features belonging to the same hurricane as the feature
// returned from the hitTest
highlight = layerView.highlight(graphic);
} else {
// remove the highlight if no features are
// returned from the hitTest
highlight.remove();
highlight = null;
}
}
});
});
I even tried creating functions that change the symbol on those polygon graphics, but to no success.
I think, that the else statement at the end is never executed. Which means, that the hitTest almost always returns a result. I debugged this a little and I saw, that the results are streets, buildings and such. After filtering those results I get the correct graphics I wish to highlight. However, layerView.highlight(graphic) doesn't work. This is how I create my mapView:
view = new MapView({
container: "viewDiv",
map: map,
center: center, // longitude, latitude
zoom: 15,
highlightOptions: {
color: "orange"
}
});
When I take the hovered graphic and change its' symbol to orange, it gets highlighted, but doesn't get unhighlighted once I unhover the element (since the else{} statement is almost never executed). Can anyone explain why hitTest always returns a result and why .highlight() doesn't work?
You problem is that you are highlighting when highlight is null, that is the first hit at start or after no feature is hit. You need to change,
if (highlight) {
return;
}
for
if (highlight) {
highlight.remove();
}
That means, if there is a feature highlighted, then remove it. Then highlight the new hit feature.
hitTest
returns the topmost feature from each layer that intersects the
specified screen coordinates
(ArcGIS Doc - MapView hitTest).
Here is the code modification from the ArcGIS example,
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="initial-scale=1,maximum-scale=1,user-scalable=no"
/>
<title>Access features with pointer events - 4.14</title>
<style>
html,
body,
#viewDiv {
padding: 0;
margin: 0;
height: 100%;
width: 100%;
}
</style>
<link
rel="stylesheet"
href="https://js.arcgis.com/4.14/esri/themes/light/main.css"
/>
<script src="https://js.arcgis.com/4.14/"></script>
<script>
require([
"esri/Map",
"esri/views/MapView",
"esri/layers/FeatureLayer"
], function(Map, MapView, FeatureLayer) {
const hurricanesLayer = new FeatureLayer({
url:
"https://sampleserver6.arcgisonline.com/arcgis/rest/services/Hurricanes/MapServer/1",
outFields: ["*"]
});
const map = new Map({
basemap: "dark-gray",
layers: [hurricanesLayer]
});
const view = new MapView({
container: "viewDiv",
map: map,
center: [-61.125537, 35.863534],
zoom: 4,
highlightOptions: {
color: "orange"
}
});
view
.when()
.then(function() {
return hurricanesLayer.when();
})
.then(function(layer) {
const renderer = layer.renderer.clone();
renderer.symbol.width = 4;
renderer.symbol.color = [128, 128, 128, 0.8];
layer.renderer = renderer;
// Set up an event handler for pointer-down (mobile)
// and pointer-move events (mouse)
// and retrieve the screen x, y coordinates
return view.whenLayerView(layer);
})
.then(function(layerView) {
view.on("pointer-move", eventHandler);
view.on("pointer-down", eventHandler);
function eventHandler(event) {
// the hitTest() checks to see if any graphics in the view
// intersect the x, y coordinates of the pointer
view.hitTest(event).then(getGraphics);
}
let highlight, currentYear, currentName;
function getGraphics(response) {
// the topmost graphic from the hurricanesLayer
if (response.results.length) {
const features = response.results.filter(function(result) {
return result.graphic.layer === hurricanesLayer;
});
if (features.length) {
const graphic = features[0].graphic;
if (highlight) {
highlight.remove();
}
highlight = layerView.highlight(graphic);
return;
}
}
// remove the highlight if no features, of the layer, are
// returned from the hitTest
highlight.remove();
highlight = null;
}
});
});
</script>
</head>
<body>
<div id="viewDiv"></div>
</body>
</html>
i know it is possible to draw circle or rectangle on tile images when using leaflet.js. here is one link http://jsfiddle.net/tridip/p6ssbvqj/
leaflet has function called circle() polygon() etc
my interface is like that i have 4 button one is circle,rectangle,load image, save image.
when page will load first time then i will show a image by leaflet.js and when user click on circle or rectangle button then i have to allow user to draw a circle or rectangle on image. the question which jquery or any javascript library i should use which will allow to draw a circle or rectangle on image ?
next i need to store those coordinate of circle or rectangle at client side because later i could save those info in db.
3rd one when i will reload images again i need to show drawn circle or rectangle on same image and in same location where user has drawn.
please guide me how to achieve it. i have never done before so no idea i have. please help me. thanks
EDIT 1
var drawnItems = new L.FeatureGroup();
map.addLayer(drawnItems);
1) What is the meaning of L.FeatureGroup()?
What does L.FeatureGroup() do?
2) What does the code below do?
var drawControl = new L.Control.Draw({
draw: {
position: 'topleft',
polygon: {
allowIntersection: false,
drawError: {
color: '#b00b00',
timeout: 1000
},
shapeOptions: {
color: '#bada55'
},
showArea: true
},
polyline: {
metric: false
},
circle: {
shapeOptions: {
color: '#662d91'
}
}
},
edit: {
featureGroup: drawnItems
}
});
map.addControl(drawControl);
what the above code is doing. it seems that the above code is trying to draw control on map programmatically. may be i am not right.
because if the above line is related to draw control on map programmatically then there should be coordinate or something relavent should be there but i have not
found anything in the above code. so please tell me what the above code is doing ?
3) if i need to draw any shape on map then do i need to first add any layer on map because as per your code you first add layer by this line
map.addLayer(drawnItems);
4) the below code is clear
if (type === 'marker') {
coords = JSON.stringify(layer._latlng);
}
the above code storing lat and lang as coordinate in variable but you have missded to show another set of code where i will provide
lat and lang details as coordinate then code will draw same shape in right position as per lat & lang value.
please read my all 4 point and please write answer to drive out my confusion. specially point 1 & 2 related code is not clear to me
and next give me code where i will pass shape name and latlang then leaflet api will draw shape accordingly.
thanks
As gusper noted, Leaflet.draw is a ready-made library for interactive drawing on Leaflet maps. Here's their demo slightly modified to display the coordinates of shapes drawn on the map.
If necessary, you can store these coordinates in a DB, and then use the native Leaflet functions to re-draw these shapes from the list of coordinates.
var osmUrl = 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
osmAttrib = '© OpenStreetMap contributors',
osm = L.tileLayer(osmUrl, {
maxZoom: 18,
attribution: osmAttrib
}),
map = new L.Map('map', {
layers: [osm],
center: new L.LatLng(-37.7772, 175.2756),
zoom: 15
});
var drawnItems = new L.FeatureGroup();
map.addLayer(drawnItems);
var drawControl = new L.Control.Draw({
draw: {
position: 'topleft',
polygon: {
allowIntersection: false,
drawError: {
color: '#b00b00',
timeout: 1000
},
shapeOptions: {
color: '#bada55'
},
showArea: true
},
polyline: {
metric: false
},
circle: {
shapeOptions: {
color: '#662d91'
}
}
},
edit: {
featureGroup: drawnItems
}
});
map.addControl(drawControl);
map.on('draw:created', function(e) {
var type = e.layerType;
var layer = e.layer;
var coords;
console.log(e);
if (type === 'marker') {
coords = JSON.stringify(layer._latlng);
}
if (type === 'circle') {
coords = JSON.stringify(layer._latlng) + " " + layer._mRadius;
}
if (type === 'rectangle') {
coords = JSON.stringify(layer._latlngs);
}
if (type === 'polygon') {
coords = JSON.stringify(layer._latlngs);
}
if (type === 'polyline') {
coords = JSON.stringify(layer._latlngs);
}
document.getElementById("coords").innerHTML = coords;
drawnItems.addLayer(layer);
});
<head>
<title>Leaflet Draw</title>
<link rel="stylesheet" href="http://leaflet.github.io/Leaflet.draw/lib/leaflet/leaflet.css" />
<link rel="stylesheet" href="http://leaflet.github.io/Leaflet.draw/leaflet.draw.css" />
<!--[if lte IE 8]>
<link rel="stylesheet" href="lib/leaflet/leaflet.ie.css" />
<link rel="stylesheet" href="leaflet.draw.ie.css" />
<![endif]-->
<script src="http://leaflet.github.io/Leaflet.draw/lib/leaflet/leaflet.js"></script>
<script src="http://leaflet.github.io/Leaflet.draw/leaflet.draw.js"></script>
</head>
<body>
<div id="map" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;"></div>
<div id="coords" style="position: fixed; bottom: 0; right: 0; width: 50%; height: 20%; z-index: 99; background-color: white; text-wrap: "></div>