Polygon rendering with pixijs - svg

I'm trying to display more than 6000 Polygons on a mobile device.
Currently, I'm doing this with SVG paths in Android WebView using the d3.js library.
It works but I have to deal with performance issues, my map becomes very laggy when I drag my map or zoom.
My Idea now is to try the same with pixijs. My data comes originally from ESRI Shapefiles. I'm convert these Shapefiles to GeoJSON and then to SVG. My array of vertices looks like this, which I'm trying to pass to the drawPolygon function
0: 994.9867684400124
1: 22.308409862458518
2: 1042.2789743912592
3: 61.07148769269074
But when I try to render these polygon nothing being displayed. This is my code:
var renderer = PIXI.autoDetectRenderer(1800, 1800, { backgroundColor: 0x000000, antialias: true });
document.body.appendChild(renderer.view);
var stage = new PIXI.Container();
var graphics = new PIXI.Graphics();
var totalShapes = feat.features.length;
for (var i = 1; i <= totalShapes -1; i++) {
var shape = feat.features[i];
var geometry = shape.geometry.bbox;
graphics.beginFill(0xe74c3c);
graphics.drawPolygon([ geometry]);
graphics.endFill();
stage.addChild(graphics);
renderer.render(stage);
}
Can someone help me or could suggest me a different way?

I have not seen that way of initializing a pixie project.
Usually you add the application to the html document like:
var app = new PIXI.Application({
width: window.innerWidth,
height: window.innerHeight,
backgroundColor: 0x2c3e50
});
document.body.appendChild(app.view);
If you do this you can add your draw calls to the setup of the application:
app.loader.load(startup);
function startup()
{
var g = new PIXI.Graphics();
g.beginFill(0x5d0015);
g.drawPolygon(
10, 10, 120, 100, 120, 200, 70, 200
);
g.endFill();
app.stage.addChild(g);
}
This will render the polygon once.

Related

Fixed SVG Layer Position on Open Layers Map

I have a map with multiple Layers. One of them shall project an SVG on the map, indicating certain regions (if not obvious, it's the red area in the images). I got this working on the default zoom level.
But as soon as I change the zoom in any direction, the SVG layer just moves too much in a direction depending on where I initiate the zoom change.
This is the relevant code I have:
//Map init
var mapcenter = ol.proj.fromLonLat([10.615219, 51.799502]);
var layers = [];
var map = new ol.Map({
target: 'map',
layers: [
new ol.layer.Tile({
source: new ol.source.OSM()
})
],
view: new ol.View({
center: mapcenter,
zoom: 9.5
})
});
//SVG init
const svgContainer = document.createElement('div');
const xhr = new XMLHttpRequest();
xhr.open('GET', '/shared/harzregionen.svg');
xhr.addEventListener('load', function () {
const svg = xhr.responseXML.documentElement;
svgContainer.ownerDocument.importNode(svg);
svgContainer.appendChild(svg);
});
xhr.send();
const width = 350;
const height = 0;
const svgResolution = 360 / width;
svgContainer.style.width = width + 'px';
svgContainer.style.height = height + 'px';
svgContainer.className = 'svg-layer';
//SVG Layer
map.addLayer(
new ol.layer.Vector({
render: function (frameState) {
const scale = svgResolution / frameState.viewState.resolution;
const center = frameState.viewState.center;
const size = frameState.size;
const cssTransform = ol.transform.composeCssTransform(
300,
120,
480 / frameState.viewState.resolution,
480 / frameState.viewState.resolution,
frameState.viewState.rotation,
-(center[0] - mapcenter[0]) / 420, //These values were kinda
(center[1] - mapcenter[1]) / 250 //trial and error for default zoom
);
svgContainer.style.transform = cssTransform;
svgContainer.style.opacity = this.getOpacity();
return svgContainer;
}
})
);
I'm pretty sure I have to use a certain formula to calculate the values in composeCssTransform but it feels like there should be an easy way to do it instead of trial and error until it somewhat works.
So, my question is: Is there an easy way to lock an SVG layer to a set position on the world map like the layers with markers?

Phaser 3. Change dimensions of the game during runtime

Hi please help me to find out how trully responsive game can be created with Phaser3.
Respnsiveness is critical because game (representation layer of Blockly workspace) should be able to be exapanded to larger portion on screen and shrinked back many times during the session.
The question is How I can change dimentions of the game in runtime?
--- edited ---
It turns out there is pure css solution, canvas can be ajusted with css zoom property. In browser works well (no noticeable effect on performance), in cordova app (android) works too.
Here is Richard Davey's answer if css zoom can break things:
I've never actually tried it to be honest. Give it a go and see what
happens! It might break input, or it may carry on working. That (and
possibly the Scale Manager) are the only things it would break,
though, nothing else is likely to care much.
// is size of html element size that needed to fit
let props = { w: 1195, h: 612, elementId: 'myGame' };
// need to know game fixed size
let gameW = 1000, gameH = 750;
// detect zoom ratio from width or height
let isScaleWidth = gameW / gameH > props.w / props.h ? true : false;
// find zoom ratio
let zoom = isScaleWidth ? props.w / gameW : props.h / gameH;
// get DOM element, props.elementId is parent prop from Phaser game config
let el = document.getElementById(props.elementId);
// set css zoom of canvas element
el.style.zoom = zoom;
Resize the renderer as you're doing, but you also need to update the world bounds, as well as possibly the camera's bounds.
// the x,y, width and height of the games world
// the true, true, true, true, is setting which side of the world bounding box
// to check for collisions
this.physics.world.setBounds(x, y, width, height, true, true, true, true);
// setting the camera bound will set where the camera can scroll to
// I use the 'main' camera here fro simplicity, use whichever camera you use
this.cameras.main.setBounds(0, 0, width, height);
and that's how you can set the world boundary dynamically.
window.addEventListener('resize', () => {
game.resize(window.innerWidth, window.innerHeight);
});
There is some builtin support for resizing that can be configured in the Game Config. Check out the ScaleManager options. You have a number of options you can specify in the scale property, based on your needs.
I ended up using the following:
var config = {
type: Phaser.AUTO,
parent: 'game',
width: 1280, // initial width that determines the scaled size
height: 690,
scale: {
mode: Phaser.Scale.WIDTH_CONTROLS_HEIGHT ,
autoCenter: Phaser.Scale.CENTER_BOTH
},
physics: {
default: 'arcade',
arcade: {
gravity: {y: 0, x: 0},
debug: true
}
},
scene: {
key: 'main',
preload: preload,
create: this.create,
update: this.update
},
banner: false,
};
Just in case anybody else still has this problem, I found that just resizing the game didn't work for me and physics didn't do anything in my case.
To get it to work I needed to resize the game and also the scene's viewport (you can get the scene via the scenemanager which is a property of the game):
game.resize(width,height)
scene.cameras.main.setViewport(0,0,width,height)
For Phaser 3 the resize of the game now live inside the scale like this:
window.addEventListener('resize', () => {
game.scale.resize(window.innerWidth, window.innerHeight);
});
But if you need the entire game scale up and down only need this config:
const gameConfig: Phaser.Types.Core.GameConfig = {
...
scale: {
mode: Phaser.Scale.WIDTH_CONTROLS_HEIGHT,
},
...
};
export const game = new Phaser.Game(gameConfig);
Take a look at this article.
It explains how to dynamically resize the canvas while maintaining game ratio.
Note: All the code below is from the link above. I did not right any of this, it is sourced from the article linked above, but I am posting it here in case the link breaks in the future.
It uses CSS to center the canvas:
canvas{
display:block;
margin: 0;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
It listens to the window 'resize' event and calls a function to resize the game.
Listening to the event:
window.onload = function(){
var gameConfig = {
//config here
};
var game = new Phaser.Game(gameConfig);
resize();
window.addEventListener("resize", resize, false);
}
Resizing the game:
function resize() {
var canvas = document.querySelector("canvas");
var windowWidth = window.innerWidth;
var windowHeight = window.innerHeight;
var windowRatio = windowWidth / windowHeight;
var gameRatio = game.config.width / game.config.height;
if(windowRatio < gameRatio){
canvas.style.width = windowWidth + "px";
canvas.style.height = (windowWidth / gameRatio) + "px";
}
else{
canvas.style.width = (windowHeight * gameRatio) + "px";
canvas.style.height = windowHeight + "px";
}
}
I have used and modified this code in my phaser 3 project and it works great.
If you want to see other ways to resize dynamically check here
Put this in your create function:
const resize = ()=>{
game.scale.resize(window.innerWidth, window.innerHeight)
}
window.addEventListener("resize", resize, false);
Important! Make sure you have phaser 3.16+
Or else this won't work!!!
Use the game.resize function:
// when the page is loaded, create our game instance
window.onload = () => {
var game = new Game(config);
game.resize(400,700);
};
Note this only changes the canvas size, nothing else.

openlayers polygon change color

i'm using OL3 and javascript to draw several polygon on a map. Each polygon came from a database in WKT format like "POLIGON((39 -9, ....))". I can draw them on the map but i want to change fill color of each one, but don't know how to do it.
Here is my code:
//WKTpoly -> this is my array of POLYLINES
var format = new ol.format.WKT();
var vectorArea = new ol.source.Vector({});
for (var i=0;i<WKTpoly.length;i++) {
var featureGeom = format.readFeature(WKTpoly[i]);
featureGeom.getGeometry().transform('EPSG:4326', 'EPSG:3857');
vectorArea.addFeature(featureGeom);
}
VectorMap = new ol.layer.Vector({
name: map,
source: vectorArea,
});
map.addLayer(VectorMap);
Well, after LessThanJake response and some more google search, i found the solution, i had to create a style and call setStyle() before addFeature():
(...)
var style = new ol.style.Style({
fill: new ol.style.Fill({
color: FillColor,
weight: 1
}),
stroke: new ol.style.Stroke({
color: LineColor,
width: 1
})
});
featureGeom.setStyle(style);
(...)
Thanks LessThanJake for pointing the right direction.
Or you can setup the layer to use a function as style
the function signature is
var makeStyle = function(feature,resolution) {
return [styles];
};
You can use this to manage style by feature and resolution (zoom level).
As the function is called at each feature render, you'll need to cache the style in a js object to gain performance.

Can't load SVG from URL

I want to load different shapes in FabricJS based Canvas using loadSVGFromURL(), but can't. Documentation is also not complete on this. I just want a complete example. I can load it from string, but string creates spaces which creates problems when rendering. Here is my code:
var canvas = new fabric.Canvas('canvas');
fabric.loadSVGFromURL('BlueFlower.svg', function (objects) {
var SVG = fabric.util.groupSVGElements(objects, options);
canvas.add(SVG).centerObject(SVG).renderAll();
SVG.setCoords();
});
I have done an example on jsfiddle, that I create two objects and I load an svg image from url, you can take a look.
Here is an example
The snippet for load svg is this:
var site_url = 'http://fabricjs.com/assets/1.svg';
fabric.loadSVGFromURL(site_url, function(objects) {
var group = new fabric.PathGroup(objects, {
left: 165,
top: 100,
width: 295,
height: 211
});
canvas.add(group);
canvas.renderAll();
});

Draw a rectangle with pixi.js

[Chrome v32]
How to draw a basic RED rectangle with PIXI.js library ?
I tried this (not working)
var stage = new PIXI.Stage(0xFFFFFF);
var renderer = PIXI.autoDetectRenderer(400, 300);
document.body.appendChild(renderer.view);
renderer.render(stage);
var rect = new PIXI.Rectangle(100, 150, 50, 50);
stage.addChild(rect);
Error:
Uncaught TypeError: Object [object Object] has no method
'setStageReference'
You can't render geometry (Pixi.Rectangle), they are meant for calculations only. You can do the following instead:
var graphics = new PIXI.Graphics();
graphics.beginFill(0xFFFF00);
// set the line style to have a width of 5 and set the color to red
graphics.lineStyle(5, 0xFF0000);
// draw a rectangle
graphics.drawRect(0, 0, 300, 200);
stage.addChild(graphics);
source
Geometries are not renderable, they are for doing geometric
calculations.
Source #xerver
So we have to use PIXI.Graphics()
There is a nice way to do this using PIXI.Texture.WHITE.
const rectangle = PIXI.Sprite.from(PIXI.Texture.WHITE);
rectangle.width = 300;
rectangle.height = 200;
rectangle.tint = 0xFF0000;
stage.addChild(rectangle);

Resources