Openlayers map tiles are not initially loaded in single page app - javascript

I have a single page application built with
Express (4.16.3),
Openlayers (5.3) and
Pug (2.0.3 – formerly known as Jade) templating engine.
The map container is loaded and has child elements with ol- classes as well as the zoom controls in the upper left corner. So the Openlayers script is successfully executed. But it doesn't show the map tiles on load.
When I resize the browser the map tiles show up all of a sudden. So I'm wondering:
What is the event that triggers the sudden rendering of the tiles on browser resize, and how can I trigger it myself so the map is getting displayed correctly on load?
My index.pug looks like this:
doctype html
html(lang='de')
head
meta(charset='UTF-8')
meta(http-equiv='X-UA-Compatible', content='ie=edge')
meta(
name='viewport'
content='width=device-width, initial-scale=1')
title=`myTitle`
// Stylesheets
link(
rel='stylesheet',
href='https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.3.0/css/ol.css')
link(
rel='stylesheet',
href='/assets/style.css')
// Scripts
script(src='https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.3.0/build/ol.js')
script(src='https://cdn.polyfill.io/v2/polyfill.min.js?features=requestAnimationFrame,Element.prototype.classList')
script(src='https://code.jquery.com/jquery-3.3.1.min.js')
script(data-main='/js/main', src='/js/require.js')
body
include header
include tabs
main
#loader Loading...
include map
include footer
And in the main part you see the map pug template included that looks like this:
section.component#component-map
#map.map
script.
/**
* Leaflet Map
*/
// Create markers and geodata
const mapCenter = [13.429, 52.494];
const siteData = !{JSON.stringify(sites)};
const features = siteData.map(site => {
return {
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [ Number(site.longitude), Number(site.latitude) ]
}
}
});
const image = new ol.style.Circle({
radius: 5,
fill: null,
stroke: new ol.style.Stroke({color: "red", width: 1})
});
const styles = {
"Point": new ol.style.Style({
"image": image
})
}
const styleFunction = function(feature) {
return styles[feature.getGeometry().getType()];
};
const geojsonObject = {
"type": "FeatureCollection",
"features": features
};
const vectorSource = new ol.source.Vector({
features: (new ol.format.GeoJSON()).readFeatures(geojsonObject)
});
const vectorLayer = new ol.layer.Vector({
source: vectorSource,
style: styleFunction
});
const map = new ol.Map({
target: "map",
layers: [
new ol.layer.Tile({
source: new ol.source.OSM()
}),
vectorLayer
],
view: new ol.View({
center: ol.proj.fromLonLat(mapCenter),
zoom: 17,
})
});

Found the answer myself. My "solution" is more of a workaround than an actual solution. I wait for the map script to be executed (which is the case when the map container has a child element with class name ol-viewport) and then I trigger the browser resize event manually. Other than I expected, the map.render() or map.renderSync() methods do not load the tiles.
So the workaround looks like this:
const waitForMap = setInterval(function() {
if ($('.ol-viewport').length) {
console.log("Exists!");
window.dispatchEvent(new Event('resize'));
clearInterval(waitForMap);
}
}, 100);

A bit ugly but this solution saved me. I'm not using jQuery so replaced .length() with:
const waitForMap = setInterval(function() {
const e = document.querySelector(".ol-viewport");
const cr = e.getClientRects();
if ((cr.length != 0) && (cr[0].width != 0) && (cr[0].height != 0)) {
console.log("Render OpenLayer map");
window.dispatchEvent(new Event('resize'));
clearInterval(waitForMap);
}
}, 500);

Related

How to filter a feature layer attributes using custom criteria in ArcGIS Esri map?

I have a parcel layer integrated into my Esri Map inside the Angular application. Now I want to filter and display only the specific parcels that meet the following criteria.
building_area equals to 0, Number_of_units equals to 0 likewise.
How can I filter the feature layer according to these conditions?
.ts
const parcelLayer = new FeatureLayer({
url: this.featureLayerUrl,
});
const esriLayers = [parcelLayer,ageLayer];
const map = new Map({
basemap: 'topo-vector',
layers: esriLayers
});
const view = new MapView({
container,
map: map,
zoom: 4,
center: [-97.63, 38.34],
});
const createEsriPopupTemplate = function(layer) {
const config = {
fields: layer.fields.map(field => (
{
name: field.name,
type: field.type,
alias: formatName(field.alias)
}
)),
title: formatName(layer.title)
};
return popupUtils.createPopupTemplate(config);
}
for (const layer of esriLayers) {
view.whenLayerView(layer).then(function (layerView) {
const popupTemplate = createEsriPopupTemplate(layer)
if (!popupTemplate) {
console.log("FeatureLayer has no fields.")
} else {
layer.popupTemplate = popupTemplate;
}
});
}
You can use FeatureLayer definitionExpression property to achieve what you are aiming for.
ArcGIS JS API - FeatureLayer definitionExpression
This property let you filter the features that will be request to the server and, as a consequence, show in the map. It is a really powerful way to filter data of layers for analysis, view and performance.
In your case this should work,
const parcelLayer = new FeatureLayer({
url: this.featureLayerUrl,
definitionExpression: "building_area=0 AND Number_of_units=0"
});

How to access ArcGIS JavaScript API outside of require callback?

I am trying to figure out how I can access the ArCGIS JS API from a map after the map has been rendered, outside of require (ArcGIS JS API uses Dojo). For example, so I can do stuff like add (or remove) points, and perform other operations on the map.
I can create a map as follows:
require(["esri/config", "esri/Map", "esri/views/MapView", "esri/Graphic",
"esri/layers/GraphicsLayer"
], function(esriConfig, Map, MapView, Graphic, GraphicsLayer) {
esriConfig.apiKey = "";
const map = new Map({
basemap: "arcgis-topographic"
});
const view = new MapView({
map: map,
center: [-81, 41],
zoom: 9,
container: "viewDiv"
});
});
And I can add a point using this function:
function plotPoint(lat, long, props) {
const popupTemplate = {
title: "{Name}",
content: "{Description}"
}
const attributes = {
Name: props.name,
Description: props.desc
}
const graphicsLayer = new GraphicsLayer();
map.add(graphicsLayer);
const point = {
type: "point",
longitude: long,
latitude: lat
};
const simpleMarkerSymbol = {
type: "simple-marker",
color: [226, 119, 40],
outline: {
color: [255, 255, 255],
width: 1
}
};
const pointGraphic = new Graphic({
geometry: point,
symbol: simpleMarkerSymbol,
attributes: attributes,
popupTemplate: popupTemplate
});
graphicsLayer.add(pointGraphic);
}
But plotPoint needs to be within the require callback so it can access the referenced modules (like GraphicsLayer). I could assign it to the global window object so I could call it outside of require, but then I may run into an issue where the function is called before it's defined.
I may need to perform other operations too from other points in the code, like removing points, adding feature layers, etc. Unfortunately, this must all exist inside some legacy code, so I can't refactor the entire application.
Is there a better pattern for accessing the API outside of require?
I think that the easy way to achieve what you want, if I understand you correctly, is just to define modules and include it in you application.
A simple example base on you code would be something like this,
GraphicsManager.js
define([
"dojo/_base/declare",
"esri/Graphic",
"esri/layers/GraphicsLayer"
], function(declare, Graphic, GraphicsLayer){
return declare(null, {
plotPoint: function(lat, long, props){
// .. here the logic
return graphicsLayer;
}
});
});
main.js
require(["esri/config", "esri/Map", "esri/views/MapView", "app/GraphicsManager"
], function(esriConfig, Map, MapView, GraphicsManager) {
esriConfig.apiKey = "";
const map = new Map({
basemap: "arcgis-topographic",
});
const view = new MapView({
map: map,
center: [-81, 41],
zoom: 9,
container: "viewDiv"
});
// ... some logic to get the point data
const gm = new GraphicsManager();
map.add(gm.plotPoint(lat, long, props));
// .. some other logic
});
There you see that the main.js is where the application start, things are set there or in others modules. You know, map, layers, widgets, etc.
Then you have your other code in modules, and you use import them as required.
dojotoolkit - intro to modules

"SecurityError: This operation is insecure" when calling domtoimage.toPng() in OpenLayers example

I am currently working on adding functionality to convert an OpenLayers Map into a png file (The example is here). However, when calling domtoimage.toPng() in the below code, Firefox (Ubuntu version 68.0.2) gives me the error SecurityError: This operation is insecure. I have checked all around and no one else seems to be having this problem with the dom-to-image library, and so I am stuck on how to fix this error. My JavaScript code for the Map is very similar to the code given in the example and is given here:
<script type="text/javascript">
var extent = [0, 0, 3000, 4213];
var projection = new ol.proj.Projection({
code: 'my-image',
units: 'pixels',
extent: extent,
});
var map = new ol.Map({
controls: ol.control.defaults().extend([
new ol.control.FullScreen()
]),
layers: [
new ol.layer.Image({
source: new ol.source.ImageStatic({
attributions: 'My Image Attributions',
url: "{{record | img_url}}", // Django stuff defined earlier
projection: projection,
imageExtent: extent
})
})
],
target: 'map',
view: new ol.View({
projection: projection,
center: ol.extent.getCenter(extent),
zoom: 2,
maxZoom: 8
})
});
map.addOverlay(new ol.Overlay({
position: [0, 0],
element: document.getElementById('null')
}));
// export options for dom-to-image.
var exportOptions = {
filter: function(element) {
return element.className ? element.className.indexOf('ol-control') === -1 : true;
}
};
document.getElementById('export-png').addEventListener('click', function() {
map.once('rendercomplete', function() {
domtoimage.toPng(map.getTargetElement(), exportOptions)
.then(function(dataURL) {
var link = document.getElementById('image-download');
link.href = dataURL;
link.click();
});
});
map.renderSync();
});
The HTML is effectively the same as in the example and so I believe the problem lies somewhere in here. Perhaps it is something with using a StaticImage in the Map? Or maybe going through the Django framework tampers with it in some unknown way? I am not entirely sure, and any diagnosis/help with fixing this issue would be much appreciated.
I think there should be something like:
new ol.layer.Tile({
name: 'name',
source: new ol.source.TileWMS({
...
crossOrigin: 'anonymous' // <-- Add this to the json.
})
})
Read more:
https://openlayers.org/en/v4.6.5/apidoc/ol.source.ImageWMS.html
https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image

How do I trigger an on demand refresh/redraw of ol.Map

I have a function called "LoadMap"
rcisWebMapLoad.prototype.LoadMap = function (param1, param2) {
//Get some vector objects and create layers
var fieldVectorObjs = rcisWebMapVectorObjs.GetFieldVectorObjects(param1, param2);
var objectVectorLines = rcisWebMapVectorObjs.GetLinesVectorObjects(param1, param2, 1);
//Create Map object and add layers then insert into map div
control.map = new ol.Map({
target: 'map',
renderer: 'canvas',
layers: layers,
view: new ol.View({
projection: 'EPSG:4326',
center: [0, 0],
zoom: 8
})
});
//******* MapServer imagery ***************
var aerial = new ol.layer.Tile({
name: 'Imagery',
source: new ol.source.TileWMS({
url: mapServerPath.ResponseString,
params: { 'LAYERS': 'aerial', 'FORMAT': 'image/png', 'TILED': true },
serverType: 'mapserver'
})
});
control.map.addLayer(aerial);
}
This loads the map great!!
I have my imagery and vector objects on the map...however the problem comes when I want to switch to a different map ie.(Different imagery and vector objects)...
UPDATE:
originally I thought the map was not getting updated but in reality another map get's generated and added right under the original map...How do I reuse or replace the map object that is already there to display another map?
Because I'm using AngularJS and passing the maps parameters through a service I can not just call the page again and get the parameters from the query string as someone suggested to me before.
This seems like something that would be a main function for an online map.
Any help is greatly appreciated
Okay, so I was not able to force an on-demand refresh of the map object for OpenLayers 3.
So what I ended up doing was to destroy the map object and create a new one each time.
so for the example above it would look like this...
For angularJS users you also need to make sure that you create an empty map in your .factory load function (so there is something to destroy initially)...if you're not using angular you would just need to create the map on page load.
function rcisWebMapLoad() {
this.map = new ol.Map({});
}
rcisWebMapLoad.prototype.LoadMap = function (param1, param2) {
//Get some vector objects and create layers
var fieldVectorObjs = rcisWebMapVectorObjs.GetFieldVectorObjects(param1, param2);
var objectVectorLines = rcisWebMapVectorObjs.GetLinesVectorObjects(param1, param2, 1);
var layers = [];
Destroy map object before creating a new one
control.map.setTarget(null);
control.map = null;
//Create Map object and add layers then insert into map div
control.map = new ol.Map({
target: 'map',
renderer: 'canvas',
layers: layers,
view: new ol.View({
projection: 'EPSG:4326',
center: [0, 0],
zoom: 8
})
});
//******* MapServer imagery ***************
var aerial = new ol.layer.Tile({
name: 'Imagery',
source: new ol.source.TileWMS({
url: mapServerPath.ResponseString,
params: { 'LAYERS': 'aerial', 'FORMAT': 'image/png', 'TILED': true },
serverType: 'mapserver'
})
});
control.map.addLayer(aerial);
}

how can i dynamically update a ol.Features geometry property ( coordinates ) in OpenLayers 3

Im just getting started with OpenLayers 3 and i am trying to dynamically update a Features geometry property with coordinates, obviously there is something that i am missing out because the Feature is not moving. Here is what i god so far:
Socket.IO
socket.on('mapData', function(mapData) {
if (mapisloaded) {
latLon = ol.proj.transform([mapData.lon, mapData.lat], 'EPSG:4326', 'EPSG:3857');
// Initiate latlong object with mapData
if (centerIsRequested) {
//Center map with mapData
};
// Update marker with latlong from mapData
};
});
OpenLayers 3 based on the Vector Icon Example
var latLon = ol.proj.transform([10.904108, 59.788187], 'EPSG:4326', 'EPSG:3857');
var iconFeature = new ol.Feature({
geometry: new ol.geom.Point(latLon),
name: 'Null Island',
population: 4000,
rainfall: 500
});
var iconStyle = new ol.style.Style({
image: new ol.style.Icon(/** #type {olx.style.IconOptions} */ ({
anchor: [0.5, 46],
anchorXUnits: 'fraction',
anchorYUnits: 'pixels',
opacity: 0.75,
src: 'imgs/lc.png'
}))
});
iconFeature.setStyle(iconStyle);
var vectorSource = new ol.source.Vector({
features: [iconFeature]
});
var vectorLayer = new ol.layer.Vector({
source: vectorSource
});
var baseLayer = new ol.layer.Tile({
source: new ol.source.OSM()
});
var view = new ol.View({
center: latLon,
zoom: 18,
});
var map = new ol.Map({
target: 'map-canvas',
layers: [ baseLayer, vectorLayer ],
view: view
});
The changes are obviously not changing, but i know that magic does not exist it was just to put something down to start with.
How wouldi go forward accomplish this simple task? The only thing i want is the icon to update its position on the map when socket.io detects the new mapdata (mapData.lat, mapData.lon).
I have tried to dig into the different objects and read their properties both in the console and in the documentation, and i have searched here on Stackoverflow but sadly without luck. Do i hook into the iconFeature, or do i have to do this another way? Maybe something really easy is built in? Any help is much appreciated.
If you want to move an icon over the map, it's better you use an ol.Overlay for this. You can use marker.setPosition(coord) on each change.
A working fiddle. Click on map to change marker's position.

Categories

Resources