I have a map with multiple layers, all connected to different vector sources.
When a user selects a feature I want him to be able to delete the feature. However, I can't seem to find a way to locate the source layer the feature is from.
If I try to just remove the feature from all layers I get an error:
Vector.js:946 Uncaught TypeError: Cannot read property 'forEach' of undefined
at Vector.removeFeatureInternal (Vector.js:946)
Is there a good way of doing finding the source layer or removing features without specifying from where?
At the moment I'm catching the exceptions, but this turns unwieldy with a lot of layers and sources.
For each source, you could try to get the selected Feature. If the response is not null, the feature exists on that source.
Something along this way inside your select:
const featureId = selectedFeature.getId()
map.getLayers().getArray().forEach(layer => {
const source = layer.getSource();
if (source instanceof VectorLayer) {
const featureExists = source.getFeatureById(featureId);
if (featureExists) {
source.removeFeature(selectedFeature);
return;
}
}
})
Related
I have some code in an application that access the style of a selected feature in a KML layer. It was working in OpenLayers 3.1. I have now upgraded to 5.3.0 and it stopped working. See the relevant lines below:
var featStyle = feature.getStyleFunction().call(feature, map.getView().getResolution());
var strokeWidth = featStyle[0].getStroke().getWidth();
var strokeColor = featStyle[0].getStroke().getColor();
var fillColor = featStyle[0].getFill().getColor();
var fillOpacity = (Math.round(fillColor[3] * 100));
The line:
var featStyle = feature.getStyleFunction().call(feature, map.getView().getResolution());
Produces an error visible in the developer console:
TypeError: o.getGeometry is not a function[Learn More] KML.js:943
a KML.js:943
myFunctionName file.php:5371
onclick file.php:1
I can't find anything in the documentation or examples that shows how to properly access the KML style data for a given feature (not an entire layer/source). Is there a new way to do this or did I miss something?
Could it have to do with this?: https://github.com/IGNF/geoportal-sdk/issues/2 Plugged into Google translate it seems to say something about no longer storing style properties inside each feature but it does not seem to say where they are stored...
The KML is valid and displays on the map properly. I just can't seem to find a way to access the style data anymore.
In OpenLayers 3 and 4 a feature style function takes only a resolution argument but internally uses this so the function or call must be bound to the feature:
feature.getStyleFunction().bind(feature)(map.getView().getResolution());
or
feature.getStyleFunction().call(feature, map.getView().getResolution());
In OpenLayers 5 feature style function are similar to layer style functions and require the feature to be passed as an argument:
feature.getStyleFunction()(feature, map.getView().getResolution());
I'm attempting to use Microsoft's PowerBi-Javascript library to embed a report in a webpage. I want to apply a slicer on load, that depends on the actual page I'm on (so can't be done by defaults on the report).
The library wiki gives a way to do this by setting slicers in the config passed to the embed function. The slicer object looks something like this (from the documentation https://github.com/Microsoft/PowerBI-JavaScript/wiki/Slicers):
interface ISlicer {
// Selects a slicer to change.
selector: SlicerSelector;
// A new state of the slicer
state: ISlicerState;
}
interface ISlicerSelector {
$schema: string;
visualName: string;
}
I'm happy with setting up the state using the filtering examples given, but I'm having problems finding the visualName for the selector - I can't see it in the interface (on viewing or editing), and I've tried using the names/headers etc I can see, none of which work.
How do I find out and/or set what this visualName is?
I've not found a way in the UI to see this. Hopefully there is a better way than the below, but this does work.
However, it is possible to find out using the api, or using this library if you're able to mess around running some code locally.
In my case, I found out by (while developing locally) finding the page and then the visuals that were displaying once the report had rendered, logging the data to the console, and identifying which visual was the one I wanted.
Something like:
report.on('rendered', () => {
report.getPages().then(pages => {
pages[0].getVisuals().then(visuals => console.log(visuals))
});
});
Where in this case I only cared about the first page.
This then logged some data to the console about each visual, including co-ordinate values and visualName, so I was able to identify the one I was interested in and see its visualName property.
Confusingly, the visualName property looks more like an id (although not a guid).
You can set the Visual Title (not the Slicer Title) on the slicer under General > Title > Text.
Screenshot
Then, you can find the slicer using the visual.title property.
For example,
report.on('rendered', () => {
report.getPages().then(pages => {
pages[0].getVisuals().then(visuals => console.log(
visuals.find(visual => visual.title === "MySlicer")
);
});
});
We are running Open Layers 3.15.
Sometimes we get a dropped or failed tile.
Currently it displays nothing, (which can be confusing for our users) so we'd like to replace this with a tile that says 'no data' or something.
I've tried picking up the event and replacing the source of the tile eg
source.on('tileloaderror', function(){
source.setUrl('./images/map/failureTile.png');
});
but the problem with this is, instead of doing this on 1 tile, it does it for the entire layer, we don't want that.
Anyone know how we can do this for just the tile that failed and not the entire layer?
It's 2018, but for someone who may need this. Tested on v5.3.0
source.on('tileloaderror', function (event) {
var tileLoadFunction = function (imageTile, src) {
imageTile.getImage().src = './images/map/failureTile.png';
};
if (event.tile.tileLoadFunction_ != tileLoadFunction) {
event.tile.tileLoadFunction_ = tileLoadFunction;
event.tile.load();
}
});
This code relys on private function event.tile.tileLoadFunction_ is exposed.
Unfortunately xnakos' answer doesn't work on v5.3.0 because the event.tile.getImage() has been replaced with 1x1 canvas image by internal error handler.
Also noted, changing event.tile.src_ directly, seems to be an option, but it doesn't work either because of cache key or something.
A tile which failed to load should have a distinct class (.olImageLoadError). You can define a CSS rule not show these items.
.olImageLoadError {
display: none !important;
}
You could try this:
source.on('tileloaderror', function(event) {
event.tile.getImage().src = './images/map/failureTile.png';
});
You need the event parameter, that can get you the tile that failed, so that you can change the tile's image.
Warning: I tested the code above using tileloadend instead of tileloaderror, because my tiles never fail on me. :) I used a simulated failure rate using Math.random() and some random tiles were replaced by the image specified. I cannot think of a reason the code above would not work. If you verify it works, I will remove this warning from my answer. I tested it on OpenLayers 3.14.2 and on an OSM source.
I'm using Contentful with a MEAN stack. I query the Contentful API and get back a json object.
contentClient.entries(query, function(err, entries){
if (err) throw err;
console.log(entries);
});
I've just been receiving the following error:
[TypeError: Converting circular structure to JSON]
The object is massive (over 3000 lines when I export it from the console to a document). So I can't post it here, but I am wondering if there is a way to find where the circular structure issue is within the object and how I remedy this?
I'm a developer at Contentful and I think I can help with the second part of your question.
As for the first part, greuze's answer is the ideal thing to do if you're in node land. An alternative (that can also be helpful in the browser) is using https://www.npmjs.com/package/safe-json-stringify
As for the second part, a thing the contentful.js library does is resolve links to other entries. The raw JSON contains just an object with metadata for links, but the linked entries come in an attached includes property. The library then goes and resolves those so you don't have to do it yourself.
However, we do allow you to create circular links when linking entries to each other (and you can even link an entry to itself!) and right now we haven't implemented a good way to detect and present those in the CMS (although that's a nice feature idea that I'll propose).
So once you do find that Circular reference, that should be your root issue.
In node 0.10 and 0.12, you can do:
var obj = {"child": {}};
obj.obj = obj;
util.inspect(obj, {depth: null})
and you will get something like:
'{ child: {}, obj: [Circular] }'
Depth indicates how many times to recurse while formatting the object (2 by default), null indicates indefinitely.
To find where are circular references, it is pretty easy to look for "[Circular]" in the resulting string.
I'm not sure if this is a performant solution, but it works. We're getting this issue only when we render things on the server side using Next.js.
We have an articles model that requires related articles which end up being linked and so this crops up quite often.
Here's what I did to solve the problem:
let article = await contentful.getArticle({
'fields.slug': query.slug,
include: 2,
})
const circularRef = _.get(article, 'items[0].fields.relatedArticle.fields.relatedArticle')
if (circularRef) {
delete article.items[0].fields.relatedArticle.fields.relatedArticle
}
Note that getArticle is a helper method I created and get is from the lodash library. Hope that helps.
I've got a pretty specific question that I'm not really expecting a direct answer to, but any guidance will be helpful.
Simple and plain, I want to programmatically fire a click event on a marker positioned on a Leaflet map, powered by CartoDB. I have a single layer on the map that contains markers, and each marker has click events associated with them. So, essentially, I just need to find the marker and fire the click event on it.
For context, Mapbox actually does exactly this, but unfortunately I can not use Mapbox for this particular implementation:
https://www.mapbox.com/mapbox.js/example/v1.0.0/open-popup-externally/
I'm open to suggestions, but preferably, I'd like to do something similar to the code in the link above -- interrogate either Leaflet or CartoDB via javascript to find and access the marker via custom properties/lat-lng/??. I figure it would be simple enough to go from there.
Another way could be to hook an event when the markers are created, store them in a hash, then access that storage when I need to do my manual click. However, I don't know of any such event that exists, and I can't locate documentation that lists supported events.
Since I'm not creating the markers myself, and can not (for reasons), storing them as I add them to the map is not an option here.
I assume since Mapbox is doing it, there must be some hook, but I can't find any valuable documentation to point me in the right direction.
Any suggestions or pointers?
To anyone who stumbles upon this, I've got a workable solution for my particular case. This call will do it for you:
layer.trigger 'featureClick', event, latlng, pos, data, layer_count
Essentially, you'll want to grab the layer in question and trigger the click, passing the appropriate data:
event: this can be null, since there is no event
latlng: the lat/long position
data: an object like { cartodb_id: 123 }. The cartodb_id is required.
layer_count: the index of your layer (probably 0)
To grab the latlng and the cartodb_id here, you'll probably need to do what I did -- query for it:
function openMarker(layer, my_object_id) {
vars = { database_id: my_object_id };
opts = { format: 'geojson' };
query = "SELECT * FROM my_table WHERE my_object_id = {{ my_object_id }}"
sql = new cartodb.SQL({user: my_user_id});
sql.execute(query, vars, opts).done(function(data) {
if (data.features != undefined && data.features.length == 1)
row = data.features[0];
latlng = [ row.geometry.coordinates[1], row.geometry.coordinates[0] ];
layer.trigger('featureClick', null, latlng, null, { cartodb_id: row.properties.cartodb_id }, 0);
});
}
Then, you can just call open_marker, passing the layer you wish to open the marker on and your object identifier. As long as that's in your cartodb database, you can grab it. Of course, the query can be adjusted to suit your means.
For my particular implementation, I had to use a setTimeout call to get the flow of control right, as I do my calls on page load. But if you're calling after the page has loaded, should be no problem.
So, simple enough way to open a marker given a local identifier. Hope this helps someone else!
Kudos for the inspiration for this solution go to:
https://gist.github.com/javisantana/7b817fda1e7511c451c7#file-index-html-L39