Error using Openlayer vectortile custom renderfunction vector tiles
Openlayer layers allow to set a custom render function, as mentioned in https://openlayers.org/en/latest/apidoc/module-ol_layer_Layer-Layer.html. It says the render function takes the frame state as input and is expected to return an HTML element. This will overwrite the default rendering for the layer. I found an example of older versions of openlayers, but that does not work on openlayers 6.
I tried using CanvasVectorTileRenderer as mentioned in https://openlayers.org/en/latest/apidoc/module-ol_renderer_canvas_VectorTileLayer-CanvasVectorTileLayerRenderer.html
When not defining a custom render function everything Works smooth. But when I add the customer render function I got error message saying
VectorTileLayer.js:574 Uncaught TypeError: Cannot read properties of null (reading 'globalAlpha')
at n.renderDeclutter (VectorTileLayer.js:574)
at n.renderDeclutter (BaseVector.js:228)
at n.renderFrame (Composite.js:137)
at n.Fe (PluggableMap.js:1455)
at n.<anonymous> (PluggableMap.js:214)
My code looks like:
class customCanvasVectorTileLayerRenderer extends ol.renderer.canvas.VectorTileLayer {
constructor (frameState, layer) {
super(frameState, layer)
}
getTile(z, x, y, frameState) {
console.log("customCanvasVectorTileLayerRenderer - getTile: ", frameState, z, x, y)
}
}
urlnk = 'http://localhost:8080/geoserver/gwc/service/tms/1.0.0/' + 'GeoNetPutten:straten' +'#EPSG%3A'+'28992'+'#pbf/{z}/{x}/{-y}.pbf'
tg=ol.tilegrid.createXYZ({maxZoom: 16, minZoom: 8, extent: [-285401.92, 22598.08, 595401.9199999999, 903401.9199999999]}) //, tileSize: 256})
src= new ol.source.VectorTile({
projection: proj28992,
tileGrid: tg,
format: new ol.format.MVT({defaultDataProjection: 'EPSG:28992'}),
url: urlnk
})
var straatnamenpbf = new ol.layer.VectorTile({
title: 'Straatnamen pbf',
source: src,
render: function (frameState) {
var x = new customCanvasVectorTileLayerRenderer(this)
return x
}
})
Also when trying to directly use the existing renderer I get same error:
var straatnamenpbf = new ol.layer.VectorTile({
title: 'Straatnamen pbf',
source: src,
render: function (frameState) {
var x = new ol.renderer.canvas.VectorTileLayer(this)
return x
}
})
I guess I am mixing up things, but anyone who could help on creating a custom renderer for vectortiles in openlayers 6?
The custom getTile method is doing nothing apart from logging the passed arguments, so other inherited methods may not get the data they expect. You should either add your own custom code or call the method from the parent class, or add your own custom renderDeclutter method which will work with your getTile method
class customCanvasVectorTileLayerRenderer extends ol.renderer.canvas.VectorTileLayer {
constructor (frameState, layer) {
super(frameState, layer)
}
getTile(z, x, y, frameState) {
console.log("customCanvasVectorTileLayerRenderer - getTile: ", frameState, z, x, y);
return super.getTile(z, x, y, frameState);
}
}
Related
I am using the l-rectangle in a Vue leaflet project.
I am creating a rectangle like so:
<l-rectangle :bounds="rectangle"></l-rectangle>
which displays the rectangle on my map doing this in the .js-file:
new Vue({
el: '#app',
data: function() {
return {
rectangle: [[69.81310023846743, 16.929931640625004],[69.11310023846743, 16.129931640625004]]
}
}
});
I have created a map click event, in this function I am changing the coordinates of the rectangle array in order to make the rectangle change size/shape. But nothing is happening (the function gets called but the rectangle does not change):
clickEvent:function(event)
{
var point = [event.latlng.lat,event.latlng.lng];
this.rectangle[0] = this.rectangle[0];
this.rectangle[1] = point;
}
Thanks for any help and guidance!
As per Vue documentation, the reactivity for array will not work if you set value for a particular index, Instead you can use some array operation methods which are mentioned in Vue documentation
push()
pop()
shift()
unshift()
splice()
sort()
reverse()
the following above methods makes the array reactive
The click event can be replaced with
clickEvent:function(event)
{
var point = [event.latlng.lat,event.latlng.lng];
this.rectangle.splice(0, 1, this.rectangle[0]);
this.rectangle.splice(1, 1, point);
}
I had a complex data structure and Vue handled it fine. Then I refactored it and suddenly nothing seems to work. I verified that the data structure was correctly assembled but nothing displays. Vue didn't report any errors and nothing is displayed.
After several hours I found that the Vue render system does not work when the data structure contains a circular reference. To verify I defined two classes A and B. Each can have a reference to the other.
classA.js
export default class classA {
constructor(name) {
this.name = name
this.refB = undefined
}
get name() {return this._name}
set name(n) { this._name= n}
get type() {return 'classA'}
get refB() {return this._refB}
set refB(n) { this._refB= n}
}
classB.js
export default class classB {
constructor(name) {
this.name = name
this.refA = undefined
}
get name() {return this._name}
set name(n) { this._name= n}
get type() {return 'classB'}
get refA() {return this._refA}
set refA(n) { this._refA= n}
}
Next define a Vue component that displays a list of class A. Construct the list in the mount lifecycle hook and be sure to NOT link the instance of classB back to classA. Verify the following renders a raw display of each element of the classAList
<template lang="pug">
div Circular References
div ClassAList length: {{classAlist.length}}
div(v-for="cA in classAlist")
div {{cA}}
</template>
<script>
import ClassA from '../classA'
import ClassB from '../classB'
export default {
data () {
return {
classAlist: []
}
},
mounted: function () {
let a = new ClassA('a1')
let b = new ClassB('b1')
a.refB = b
// Uncomment the next line to create a circular reference in the
// data structure. Immediately, once this next line establishes the
// circular reference the Vue render system fails, silently
// b.refA = a
this.classAlist.push(a)
console.log('Console listing the classA list ', this.classAlist)
}
}
</script>
Check the console to see the array is constructed as expected. Now uncomment the line of code that links the instance of B back to the instance of A. The console will continue to show the array is constructed as expected but the Vue render system doesn't like the circular references.
Is there a workaround?
Is there a way to get Vue to show some error message. (e.g. like JSON.stringfy does on objects like this)?
I am trying to learn Vue.js. I am following a tutorial on this site https://scrimba.com/p/pZ45Hz/c7anmTk. From here I am not getting something clear.
Here is the code below and my confusion as well :
<div id="app">
<wizard :name="harry" :cast="oculus_reparo" ></wizard>
<wizard :name="ron" :cast="wingardium_leviosa"></wizard>
<wizard :name="hermione" :cast="alohomora" ></wizard>
</div>
// emojify returns the corresponding emoji image
function emojify(name) {
var out = `<img src="emojis/` + name + `.png">`
return out
}
// cast returns a spell (function) that decorates the wizard
function cast(emoji) {
var magic = emojify("magic")
return function (wizard) {
return wizard + " " + magic + " " + emoji + " " + magic
}
}
Vue.component("wizard", {
props: ["name", "cast"],
template: `<p v-html="cast(name)"></p>`
})
var app = new Vue({
el: "#app",
data: {
harry : emojify("harry" ),
ron : emojify("ron" ),
hermione : emojify("hermione")
},
methods: {
// oculus_reparo returns a spell (function) that repairs glasses
oculus_reparo: cast(emojify("oculus-reparo")),
// wingardium_leviosa returns a spell (function) that levitates an object
wingardium_leviosa: cast(emojify("wingardium-leviosa")),
// alohomora returns a spell (function) that unlocks a door
alohomora: cast(emojify("alohomora"))
}
})
So far what I have got is that, I have created a component named wizard which takes two properties - name and cast. name is getting the value from data, and so far I understand that cast is calling the method with a parameter.
So both of them should return their specific image. My first confusion: Where does wizard come from and how is it showing the data.name image? If it is because of the method call in the template then why does emoji return another image?
I think the example is unnecessarily complex for the ideas you're looking to learn.
wizard is being globally registered with Vue by Vue.component("wizard", ...). When Vue interprets each wizard call in the template it will replace it with <p v-html="cast(name)"></p> which is set in the wizard component definition. Here name gets mapped to the property that is set via :name=. v-html is just saying to render as html the return value of cast(name), here cast is the function property that is passed to the component and not the cast function locally defined. Everything after that happens as you would expect where emojify returns a template literal that is passed to cast, that then returns a function, which combines the emoji and other properties.
I have written a custom component to create a html button.
custom component is defined as follows
dojo.provide("ovn.form.OvnButton") ;
require([ "dojo/_base/declare",
"dojo/dom-construct",
"dojo/parser",
"dojo/ready",
"dijit/_WidgetBase"],
function (declare, domConstruct, parser, ready, _WidgetBase){
return declare ("ovn.form.OvnButton",[_WidgetBase],{
label: "unknown",
constructor : function(args){
this.id = args.id;
args.props.forEach(function(prop) {
if(prop.name == 'label'){
this.label = prop.value;
alert("found label " + this.label);
}
});
alert("from constructor " + this.label);
},
postMixInProperties : function(){
},
buildRendering : function(){
alert("from renderer label is " + this.label);
this.domNode = domConstruct.create("button", { innerHTML: this.label }); //domConstruct.toDom('<button>' + this.label + '</button>');
},
_getLabelAttr: function(){
return this.label;
},
_setLabelAttr : function(label){
alert("from set input is " + label)
this.label = label;
},
postCreate : function(){
alert("Post create label is " + this.label);
},
startUP : function(){
}
});
});
This is how I am instantiating the component
var button = new ovn.form.OvnButton({
id:'run',
props:[{"name":"label","value":"Run"},{"name":"class","value":"btn"}]
});
In the constructor of the custom component, I am iterating through the array passed and assigning to the instance variable called 'label'. To my surprise when we print the instance variable in buildRendering function, it is still printing the default instead of the assigned value.
can somebody give some light on why this is so.
FYI:
I am getting the following sequence of messages on the console
1.found label Run
2. from constructor unknown
3. from renderer label is unknown
4. from set input is unknown
5. Post create label is unknown
This happens because inside the little forEach function, this actually points to something completely different than your OvnButton object.
Javascript's this keyword is quite strange in this regard (it doesn't have anything to do with Dojo, actually). You can read more about how it works here: http://howtonode.org/what-is-this . It's a quite fundamental concept of Javascript, different from other languages, so it's well worth your time to get familiar with.
But there are various different ways you can quickly solve it, so here are a few!
Use a regular for loop instead of forEach and callback
The easiest is probably to use a regular for loop instead of forEach with a callback.
....
// args.props.forEach(function(prop) {
for(var i = 0, l = args.props.length; i < l; i++) {
var prop = args.props[i];
if(prop.name == 'label'){
this.label = prop.value;
alert("found label " + this.label);
}
}//); <-- no longer need the closing parenthesis
The takeaway here is that Javascript's this magic only happens for function calls, so in this case, when we just use a for loop, this continues to point to the right thing.
... or use forEach's second thisArg argument
But perhaps you really want to use forEach. It actually has a second argument, often called thisArg. It tells forEach to make sure this points to something of your choice inside the callback function. So you would do something like this:
....
args.props.forEach(function(prop) {
if(prop.name == 'label'){
this.label = prop.value;
alert("found label " + this.label);
}
}, this); // <--- notice we give forEach two arguments now,
// the callback function _and_ a "thisArg" value
I'm not completely sure the above works in all browsers though, so here's another way to solve your issue:
... or use a temporary "self" variable
We will make a temporary variable equal to this. People often call such a variable self, but you can name it anything you want. This is important: it's only the this keyword that Javascript will treat differently inside the callback function:
....
var self = this; //<--- we basically give `this` an alternative
// name to use inside the callback.
args.props.forEach(function(prop) {
if(prop.name == 'label'){
self.label = prop.value; //<--- replaced `this` with `self`
alert("found label " + self.label); //<--- here as well
}
});
... or use hitch() from dojo/_base/lang
Some people don't like the self solution, perhaps because they like to consistently use this to refer to the owning object. Many frameworks therefore have a "bind" function, that makes sure a function is always called in a particular scope. In dojo's case, the function is called hitch. Here's how you can use it:
require([....., "dojo/_base/lang"], function(....., DojoLang) {
....
args.props.forEach(DojoLang.hitch(this, function(prop) {
if(prop.name == 'label'){
this.label = prop.value;
alert("found label " + this.label);
}
}));
... or use Javascript's own bind()
Dojo and pretty much every other framework out there has a hitch() function. Because it's such a commonly used concept in Javascript, the new Javascript standard actually introduces it's own variant, Function.prototype.bind(). You can use it like this:
....
args.props.forEach(function(prop) {
if(prop.name == 'label'){
this.label = prop.value;
alert("found label " + this.label);
}
}.bind(this));
That was a very long answer for a pretty small thing, I hope it makes some sense!
I am using a chart to visualize data in a TimeboxScopedApp, and I want to update the data when scope changes. The more brute-force approach of using remove() then redrawing the chart as described here leaves me with an overlaid "Loading..." mask, but otherwise works. The natural approach of using the Highchart native redraw() method would be my preference, only I don't know how to access the actual Highchart object and not the App SDK wrapper.
Here's the relevant part of the code:
var chart = Ext.getCmp('componentQualityChart');
if (chart) {
var chartCfg = chart.getChartConfig();
chartCfg.xAxis.categories = components;
chart.setChartConfig(chartCfg);
chart.setChartData(data);
chart.redraw(); // this doesn't work with the wrapper object
} else { // draw chart for the first time
How do I go about redrawing the chart with the new data?
Assuming chart (componentQualityChart) is an instance of Rally.ui.chart.Chart, you can access the HighCharts instance like this:
var highcharts = chart.down('highchart').chart;
// Now you have access to the normal highcharts interface, so
// you could change the xAxis
highcharts.xAxis[0].setCategories([...], true);
// Or you could change the series data
highcharts.series[0].data.push({ ... }); //Add a new data point
// Or pretty much anything else highcharts lets you do
Using _unmask() removes the overlaid "Loading.." mask
if (this.down('#myChart')) {
this.remove('myChart');
}
this.add(
{
xtype: 'rallychart',
height: 400,
itemId: 'myChart',
chartConfig: {
//....
},
chartData: {
categories: scheduleStateGroups,
series: [
{
//...
}
]
}
}
);
this.down('#myChart')._unmask();