Open Layers - SVG Layer - svg

I'm stuck with a problem of blurry images despite using svg, which lead me to the official OL docs with a solution which I quite don't understand - https://openlayers.org/en/latest/examples/svg-layer.html
I've tried to follow this example, but obviously it doesn't work by just copy-pasting. I would like to know where the formula svgResolution = 360 / width comes from, as it used to calculate the scale and [x,y] transforms of the div. I'm not that familiar with using OL, so any kind of help will be appreciated.

Related

SVGPanZoom discards original viewBox

I am using SVGPanZoom to manage the zooming of an SVG image in my hybrid Android (for all intents and purposes the same behavior as in Chrome) app. While zooming works well I have found a strange issue. My original inline SVG element goes like this
<svg id='puzzle' viewBox='0 0 1600 770' preserveAspectRatio='none'
width='100vw' height='85.5vh' fill-rule='evenodd' clip-rule='evenodd'
stroke-linejoin='round' stroke-miterlimit='1.414'
xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://
www.w3.org/1999/xlink'>
Initially this SVG element is empty and gets populated programmatically from JavaScript at run time after which I initiate SVGPanZoom as follows
var panZoom = svgPanZoom('#puzzle',
{panEnabled:false,controlIconsEnabled:false,
zoomEnabled:true,dblClickZoomEnabled:true,onZoom:postZoom});
panZoom.refreshRate = 10;
panZoom.zoomScaleSensitivity = 0.02;
The problem I have run into is this - I want my SVG image to fill the available area, 100vw x 85.5vhcompletely to do which I instruct it via the preserveAspectRatio="none"attribute above along with the viewBox="0 0 1600 770" attribute. I have found that this works - so long as I don't use SVGPanZoom. As soon as I initiate panZoom thezoomBox`attribute gets stripped out and I end up with an image that does not quite behave in terms of its default stretching/filling behavior.
SVGPanZoom is widely used so I assume that this behavior is down to me not quite setting it up properly. Dipping into the code I have found SVGPanZoom creates a cacheViewBoxand then proceeds to remove the original zoomBox attribute.
Which is fine if after that zooming works and the original behavior of the application does not change which is not what I find. What am I doing wrong here?
I've also run into this issue recently. From my research, this is just how the library works. I chose to live with this limitation for now but I found a couple other libraries that may work the way you intend (I haven't tried them yet):
jquery.panzoom is a jquery library that provides this functionality and also has some nice features. I know many people try to avoid jquery but it's pretty small and may do what you want. It handles SVG but I don't know what it does with the viewBox attribute.
react-svg-pan-zoom is a react component which may be useful if you are working in react.
I've also tried the PanZoom library but this also suffers the same viewBox limitation.
A note for anyone running into this thread. In the end I abandoned SVGPanZoom and decided to eschew the route of using any pan/zoom library at all. At the same time I decided to completely stop using the SVG viewBox and handle all zooming/panning entirely on my own through SVG transforms. The core steps involved
Wrap the entire SVG contents in a group to make it easier to manage the transform. I use the id attribute gOuter for this group
Set an initial scale for the SVG to occupy the desired client rectangle. In my case I had an original viewBox of 0 0 1600 770 intended to occupy 100% of screen width and 85% of screen height. So my scaling was scaleX = 1600/window.innerWidth and scaleY = 770/)0.85*window.innerHeight).
Apply this initial transform to the wrapping outer group, gOuter.setAttribute('transform','0 0 scaleX,scaleY)
Now in order to zoom to a an object whose virtual top left hand coordinates in the original viewBox were Ox,Oy you would use the transform
gOuter.setAttribute('transform',
scale(scaleX,scaleY) translate(-Ox,-Oy) scale(2*scaleX,2*scaleY) translate(Ox,Oy))
to zoom in by a factor of x 2. The important things to understand here
In SVG transformations are applied right to left.
Here we are translating the zoom point to the top l.h.s. scaling and then translating it back to its original location.
The problem is that we also need to allow for the original level of zoom through the initial scaling so we tag that on as one last transform
This leaves you in complete control of the zooming process and as a fringe benefit the operation becomes considerably more smooth than when using a pan/zoom library.

Combining Multiple SVG Transformations

I'm new to Snap.svg and SVG and experimenting with transformations (an illustraiting plunk can be found here). Basically I'm trying to move, scale and rotate a shape according to its configuration. This is what I've found out so far:
rotating around a point is possible with rotate(angle, x, y)
there is no direct transform method to scale around a point but it can be done as described in "SVG Essentials"
However combining these transforms doesn't give me the expected result - my expected calculated center of the shape differs from the rendered one. Can anyone give me some pointers on how to correctly put these transforms together?
Regards,
Andi
To combine transforms, I would use Snaps own transformString format. I would first have a read of my previous answer on SO here, this is slightly different so posting a slightly different example and answer.
Whilst Snap can use SVGs transform strings (rotate() scale() transform()). They don't by default centre around itself for example, whereas Snaps (and Raphaels) do. This makes it a bit easier. For more complex situations, one may need to look into Matrix methods, but I think the following should be ok...
Snaps transformString uses string t (transform), s (scale), r (rotate), and you can add them repeatedly if wanted.
Here is an example of both methods, to highlight the difference.
jsfiddle here
s = Snap(400, 620);
var r1 = s.rect(0, 0, 100, 100).attr({
fill : 'blue',
stroke : 'black',
opacity: 0.5
});
var r2 = r1.clone().attr({ fill: "red" });
r1.transform('t100,100s2,2r45'); //typical Snap way, rotation/scale around centre
r2.transform('translate(100,100) scale(2,2,) rotate(45)'); //SVG way
The getBBox() method should be pretty reliable as far as I know (maybe post up a separate question on SO if you find an example where it is wrong)

RaphaelJS applying a custom transform

I am having trouble with applying a transformation to each path in my SVG. I know the exact transform I need to add as I am using a map from wikimedia and have tested it by adding it using firebug/chromebug.
I need to add:
transform="translate(0,239) scale(0.016963,-0.016963)"
This is an example of what I have:
http://jsfiddle.net/m7t4X/1/
If you inspect it, each path looks like this:
<path fill="#ff0000" stroke="#ccc6ae" d="M16760,5958C16752,5945,16745,5920,16743,5901C16740,5859,16734,5853,16710,5868C16694,5878,16690,5878,16690,5866C16690,5858,16696,5849,16703,5846C16709,5843,16705,5843,16693,5846C16670,5851,16666,5845,16669,5808C16670,5799,16666,5789,16660,5785C16655,5782,16650,5770,16650,5760C16650,5747,16645,5743,16635,5746C16626,5750,16620,5746,16620,5737C16620,5717,16549,5667,16495,5649C16473,5642,16451,5632,16446,5628C16440,5624,16426,5620,16413,5620C16395,5620,16390,5615,16390,5593C16390,5577,16381,5552,16371,5536C16351,5506,16351,5482,16371,5367C16383,5300,16383,5298,16354,5242C16338,5211,16321,5182,16317,5179C16282,5154,16269,5063,16294,5016C16302,5000,16306,4972,16303,4946C16300,4920,16302,4900,16309,4896C16315,4892,16320,4881,16320,4871C16320,4849,16350,4820,16373,4820C16382,4820,16390,4816,16390,4810C16390,4795,16442,4799,16464,4815C16475,4823,16496,4830,16511,4830C16541,4830,16580,4870,16580,4899C16580,4908,16586,4924,16594,4935C16601,4946,16613,4980,16620,5010C16628,5040,16642,5081,16651,5101C16661,5121,16675,5159,16681,5186C16688,5213,16710,5278,16731,5332C16752,5385,16772,5446,16775,5467C16787,5539,16790,5550,16803,5547C16815,5545,16816,5554,16804,5630L16797,5675L16817,5651L16837,5626L16854,5658C16866,5682,16868,5695,16860,5709C16855,5720,16850,5749,16850,5773C16850,5797,16845,5832,16839,5851C16825,5895,16786,5979,16780,5980C16777,5980,16768,5970,16760,5958Z" stroke-opacity="1" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0); stroke-opacity: 1;"></path>
If I add it manually via firebug etc to change the start to look like this:
<path transform="translate(0,239) scale(0.016963,-0.016963)" fill="#ff0000" stroke="#ccc6ae" d="
then it works perfectly. I just cannot replicate this.
I have tried various ways of adding the transform, but none have worked fully.
From what I gather, RaphaelJS is backwards compatible so all of the solutions I have found (which haven't fully worked anyway) wouldn't have worked in any older IEs for instance.
The first thing I tried was to change:
paper.setViewBox(0, 0, w, h, true);
to:
paper.setViewBox(true);
However this did not work with the shapes I am using (http://upload.wikimedia.org/wikipedia/commons/9/95/Continents.svg)
This is where I got the transform amounts from (can be seen if you view source).
Please advise on where I'm going wrong, and how I could go about adding this.
Many thanks!
---------------------------------EDIT -------------------------------------
After applying the fix below, I have found it appears to be displaying the SVG rotated 180degrees and flipped horizontally. I don't quite understand why either.
This is how it is meant to look:
And this is how it comes out:
(also noticed I'm missing south america despite it being referenced)
This is a new fiddle of how it currently looks:
http://jsfiddle.net/wrayvon/m7t4X/3/
Thanks again
The transform should work, but I think you just need to set a bigger width/height for the viewBox. These should be for the co-ordinate space, not for pixels on screen.
var w = 15000;
var h = 25000;
paper.setViewBox(10000, 0, w, h, true);
Should do it. You can also transform it if you want, by adding something like the following to the element when created.
.transform('t2000,1000')
edit: As you have changed the SVG... If there are transforms in the original, you will probably need to apply the same transforms to the elements...so a line like this...
var c = paper.path(worldmap.shapes[country]).attr({ stroke: "#ccc6ae", fill: "#ff0000", "stroke-opacity": 1 }).transform('s1,-1,13000,10000');
I'm just guessing the centre point to scale from there (13,000,10,000 its probably a bit more), you will need to figure that separately.
Or you could match the scale as per the original SVG, and then you probably won't need to change the viewBox (apart from matching the original).
Original jsfiddle
Updated jsfiddle
I have had success with the exact same problem. Your question is the clearest articulation of it.
As a bit of background, I found an SVG map of the US and all of its counties. I thought, "great, I'll just copy these paths into Raphael, and Bob's your uncle". So, I did, but it appeared exactly upside down after the import. I considered applying some math to the paths, but decided against it. Instead I investigated the transform() option too.
In the transform method, there are a few options. One of the options is to use the matrix ("m"). The matrix method has 6 options. - see here
for more details. But the matrix setting for the 'as-the-path' dictates is this:
.transform("m1,0,0,1,0,0");
If you make the 4th place negative, it will invert your image.
.transform("m1,0,0,-1,0,0");
HOWEVER...it has to choose a location for the axis on which to flip. That, as far as I can tell, is the top edge of the the container div. So your image is now above you. Use the viewbox to find it and zoom as you desire:
R.setViewBox(x,y,x-size,y-size);//The Y (second param) will need to be negative.
Here's my example to find my shape: (make your 200's into 1000's to more easily locate you image, then play with the numbers to center, and enlarge)
R.setViewBox(0,-575,200,200);
The downside using the transform is the overhead of drawing, then relocating the images. In my case, I am less concerned about this as speed doesn't seem to be affected, which may be due to the relatively small number of paths involved.

What is the Tilemill svg export resolution and zoom levels relation?

Tilemill works great, mostly very usable! I managed to get to the part where I am making svg exports of my map made with osm-bright as a starter. That seems to be fine but I cant work out how the resolution I specify for the SVG relates to the zoom levels. I guess it has something to do with the tile size, can someone help me understand this?
Thanks a lot, Joris.
First, for printing, the resolution is 90.7 ppi (some OGC standard or whatever). Some round it down to 90 ppi.
Zoom-to-scale relation is calculated in this table: 1:2132 on z18, 1:4265 on z17 and so on. Also see this page on OSM wiki for formulas.

How to set the rotation center of an MKAnnotationView

I've got a problem with my MKAnnotationViews when MKUserTrackingModeFollowWithHeading is enabled on the MKMapView.
I positioned my images using the centerOffset property of the MKAnnotationView. Specifying the coordinates of the pin's tip relative to the coordinate system at the center of the image is somewhat counter-intutive, but I came up with the following formula:
annotationView.centerOffset = CGPointMake(imageWidth/2.0 - tipXCoordinate, imageHeight/2.0 - tipYCordinate);
This works fine for zooming the map in and out. The tips of the pins keep their relative position on the map.
However, when I enable MKUserTrackingModeFollowWithHeading, it won't work anymore. The Pins rotate around the center of the image, instead of the tip. So when the map rotates, the tips do no point to the locations they are supposed to annotate.
I've played around a bit with the frameand centerproperties of the MKAnnotationView, but I feel, they are having no effect on the alignement of the pins whatsoever.
Interestingly, the MKPinAnnotationView does not seem to use centerOffset at all, but a shifted frame instead. However, I was unable to reproduce this. Changing the frame of my custom view did not move it at all.
Thanks for any insights you can provide :-)
Solution:
Don't use centerOffset! Use annotationView.layer.anchorPoint instead. The coordinate system of achor point is much nicer, too. Coordinates range from 0.0 (top/left) to 1.0 (bottom/right) of the image rectangle:
annotationView.layer.anchorPoint = CGPointMake(tipXCoordinate/imageWidth, tipYCordinate/imageHeight);
A friend asks me to let you know that you should "try this for instance":
self.layer.anchorPoint = CGPointMake (0.5f, 1.0f);

Resources