If I print UIScreen.main.nativeBounds.height it returns 2048 for my iPad simulator which is fine. But how does this relate to the x and y coordinates? For example, the middle of the screen is x: 0, y: 0. And if I move to the left on the x coordinates I use minus, like x: -100, and if I move to the right I use plus, like x: 100.
In my head this would mean that UIScreen.main.nativeBounds.height is divided by 2 so that I can move between x: -1024 and x: 1024. Is that correct? Well, it seems that it's not because this code:
portrait = gui.addPortrait(width: 80, height: 80, x:-350 , y: 180)
addChild(portrait)
will take me to the far end of the device's x-axis, like this:
meaning that the total x-axis must be something like 750 in total. What happened to 1024? I think I must misunderstand how the x-coordinates relate to the UIScreen.main.nativeBounds.height. The reason I want to understand this is because I want to set the x position dynamically so that it works on all devices. Something like x: -(UIScreen.main.nativeBounds.height / 3). What am I missing?
In contrast to UIScreen.bounds, which specifies "[t]he bounding rectangle of the screen, measured in points" (docs), UIScreen.nativeBounds specifies "[t]he bounding rectangle of the physical screen, measured in pixels" (docs).
Thus, you'll want to transform the values into points with the help of UIScreen.nativeScale:
let width = round(UIScreen.main.nativeBounds.width / UIScreen.main.nativeScale)
let height = round(UIScreen.main.nativeBounds.height / UIScreen.main.nativeScale)
You can get the height and width of the screen in points like this:
let height = UIScreen.main.bounds.height
let width = UIScreen.main.bounds.width
No point of the screen has negative coordinates:
The top left-hand corner of the screen is x: 0, y :0
The top right-hand corner of the screen is x: width, y: 0
The bottom left-hand corner of the screen is x: 0, y: height
The bottom right-hand corner of the screen is x: width, y: height
Related
I have the need to export georeferenced images from Leaflet.js on the client side. Exporting an image from Leaflet is not a problem as there are plenty of existing plugins for this, but I'd like to include a world file with the export so the resulting image can be read into GIS software. I have a working script fort his, but I can't seem to nail down the correct parameters for my world file such that the resulting georeferenced image is located exactly correctly.
Here's my current script
// map is a Leaflet map object
let bounds = map.getBounds(); // Leaflet LatLngBounds
let topLeft = bounds.getNorthWest();
let bottomRight = bounds.getSouthEast();
let width_deg = bottomRight.lng - topLeft.lng;
let height_deg = topLeft.lat - bottomRight.lat;
let width_px = $(map._container).width() // Width of the map in px
let height_px = $(map._container).height() // Height of the map in px
let scaleX = width_deg / width_px;
let scaleY = height_deg / height_px;
let jgwText = `${scaleX}
0
0
-${scaleY}
${topLeft.lng}
${topLeft.lat}`
This seems to work well at large scales (ie zoomed in to city-level or so), but at smaller scales there is some distortion along the y-axis. One thing I noticed is that all examples of world files I can find (and those produced from QGIS or ArcMap) all have the x-scale and y-scale parameters being exactly equal (oppositely signed). In my calculations, these terms are different unless you are sitting right on the equator.
Example world file produced from QGIS
0.08984380916303301 // x-scale (size of px in x direction)
0 // rotation parameter 1
0 // rotation parameter 2
-0.08984380916303301 // y-scale (size of px in y direction)
-130.8723208723141056 // x-coord of top left px
51.73651369984968085 // y-coord of top left px
Example world file produced from my calcs
0.021972656250000017
0
0
-0.015362443783773333
-130.91308593750003
51.781435604431195
Example of produced image using my calcs with correct state boundaries overlaid:
Does anyone have any idea what I'm doing wrong here?
Problem was solved by using EPSG:3857 for the worldfile, and ensuring the width and height of the map bounds was also measured in this coordinate system. I had tried using EPSG:3857 for the worldfile, but measured the width and height of the map bounds using Leaflet's L.map.distance() function. To solve the problem, I instead projected corner points of the map bounds to EPSG:3857 using L.CRS.EPSG3857.project(), the simply subtracted the X,Y values.
Corrected code is shown below, where map is a Leaflet map object (L.map)
// Get map bounds and corner points in 4326
let bounds = map.getBounds();
let topLeft = bounds.getNorthWest();
let bottomRight = bounds.getSouthEast();
let topRight = bounds.getNorthEast();
// get width and height in px of the map container
let width_px = $(map._container).width()
let height_px = $(map._container).height()
// project corner points to 3857
let topLeft_3857 = L.CRS.EPSG3857.project(topLeft)
let topRight_3857 = L.CRS.EPSG3857.project(topRight)
let bottomRight_3857 = L.CRS.EPSG3857.project(bottomRight)
// calculate width and height in meters using epsg:3857
let width_m = topRight_3857.x - topLeft_3857.x
let height_m = topRight_3857.y - bottomRight_3857.y
// calculate the scale in x and y directions in meters (this is the width and height of a single pixel in the output image)
let scaleX_m = width_m / width_px
let scaleY_m = height_m / height_px
// worldfiles need the CENTRE of the top left px, what we currently have is the TOPLEFT point of the px.
// Adjust by subtracting half a pixel width and height from the x,y
let topLeftCenterPxX = topLeft_3857.x - (scaleX / 2)
let topLeftCenterPxY = topLeft_3857.y - (scaleY / 2)
// format the text of the worldfile
let jgwText = `
${scaleX_m}
0
0
-${scaleY_m}
${topLeftCenterPxX}
${topLeftCenterPxY}
`
For anyone else with this problem, you'll know things are correct when your scale-x and scale-y values are exactly equal (but oppositely signed)!
Thanks #IvanSanchez for pointing me in the right direction :)
I am trying to make this picture appear at the bottom center but it keeps moving towards the right. I have looked at scales and none of them have worked.
I have tried many different numerals for x and y such as x=7 and y=7 or x=20 and y=5 and they all end up exactly in the middle or to the right. I also considered using negative numbers but didn't know how
player = SKSpriteNode(imageNamed: "shuttle")
player.position = CGPoint(x: self.frame.size.width/7, y: player.frame.size.height/7)
self.addChild(player)
I am trying to plot some coordinates on the earth on an UIImage which contains a map of the world. (I don't want to use maps)
See an example of the UIImageView below below:
As you see it's working out pretty well but the mapping from coordinates and X Y are incorrect!
Amsterdam's coordinates are: (52.36666489, 4.883333206) and the Center's are (0,0).
I've done the following things to try to make this happen but unfortunately this isn't working out:
I've tried first to 'normalize' the coordinates since latitude ranges from -90 to 90 and latitude -180 to 180. This is done by adding 90 to the real latitude and 180 to the real longitude which yiels the 'normalized' versions:
let normalizedLat = location.coordinate.latitude + 90.0.
let normalizedLng = location.coordinate.longitude + 180.0
After that I've calculated the scale factor where the normalizedLat and normalizedLng should scale with:
let heightScaleFactor = mapImageView.frame.height / 180.0
let widthScaleFActor = mapImageView.frame.width / 360.0
And 3. After that i've got the scaling factors I finally can calculate the coordinates by:
let x = Double(widthScaleFActor * CGFloat(normalizedLng))
let y = Double(heightScaleFactor * CGFloat(normalizedLat))
dot.frame = CGRect(x: x, y: y, width: Double(dot.frame.width), height: Double(dot.frame.height))
But for some strange reason Amsterdam is not on the Amsterdam spot and the Center is not on the Center spot.
I am quite sure that my calculations has gone wrong. Any ideas?
Remember, in iOS the origin is in the top-left, not the bottom-left. Positive-y goes down, not up.
You need to factor that in.
dot.frame = CGRect(x: x, y: mapImageView.frame.height - y, width: Double(dot.frame.width), height: Double(dot.frame.height))
Also note that the equator in your image is not in the middle. It's lower in the image so you need to add an additional offset in your calculation of the y value based on the equator's offset in the image.
dot.frame = CGRect(x: x, y: mapImageView.frame.height - y + equatorOffset, width: Double(dot.frame.width), height: Double(dot.frame.height))
It's also possible that your map projection doesn't have a simple linear latitude scale. 0-10 degrees might be 12 pixels while 10-20 degrees might be 11 pixels, etc. and 80-90 is only 3 pixels (or whatever).
I have managed to load and display an isometric map using SpriteKit and Swift by writing my own .TMX parser. Orthogonal maps work fine, but this is my first time using Isometric maps and the math is confusing me.
My tiles are 64x32, and the map can be any size.
Ideally I would like the left most edge of the map (the tile at [0, maxRow]) to be sitting at zero on the x-axis, and the bottom most edge of the may (the tile at [maxCol, maxRow]) to be sitting on the 0 y-axis.
The origin of the map is the tile at the top. X goes from Top to Right, Y goes from Top to Left.
SpriteKit also has a reversed y-axis. The code below is how i am positioning tiles based on their coords, and retrieving them using a position on screen. This code works fine.
So to offset the position correcting so I can place the map where I want I need to be able to find the width and height of an isometric map of any size and I have no idea where to start.
func positionForCoord(col: Int, _ row: Int) -> (x: CGFloat, y: CGFloat) {
var x: CGFloat = 0
var y: CGFloat = 0
x = (CGFloat(col - row) * CGFloat(tileSize.width)) / 2
y = (CGFloat(col + row) * -CGFloat(tileSize.height)) / 2
return (x, y)
}
func coordForPosition(x: CGFloat,_ y: CGFloat) -> (col: Int, row: Int) {
var col: Int = 0
var row: Int = 0
let tileWidthHalved = CGFloat(tileSize.width) / 2
let tileHeightHalved = CGFloat(tileSize.height) / 2
col = Int(floor(((x / tileWidthHalved) - ((y - tileHeightHalved) / tileHeightHalved)) / 2))
row = Int(ceil(((((y - tileHeightHalved) / -tileHeightHalved) - (x / tileWidthHalved)) / 2)))
return (col, row)
}
Since you tagged this with Tiled, one way to start is by looking at Tiled's source code to see how it calculates it. It's really only this small bit of code in isometricrenderer.cpp:
QSize IsometricRenderer::mapSize() const
{
// Map width and height contribute equally in both directions
const int side = map()->height() + map()->width();
return QSize(side * map()->tileWidth() / 2,
side * map()->tileHeight() / 2);
}
To understand it I usually draw a small piece of isometric map on paper and look at how its bounds are derived from its tile size. You would see that for each additional tile row, the map bounds grow by the same amount as for each additional tile column. And the amount is exactly half the tile size.
I'm trying to figure out what all these arguments do, as when I draw my bullet image it appears as a solid block instead of a sprite that alternates between solid color and an empty portion (i.e instead of 10101 it's 11111, with 0's being empty parts in the texture).
Before, I was using batch.draw(texture, float x, float y) and it displays the texture correctly. However I was playing around with rotation, and this is the version of draw that seemed most suitable:
batch.draw(texture, x, y, originX, originY, width, height, scaleX, scaleY, rotation, srcX, srcY, srcWidth, srcHeight, flipX, flipY)
I can figure out the obvious ones, those being originX, originY (location to draw the image from its upper left pixel I believe) however then I don't know what the x, y coordinate after texture is for.
scaleX,scaleY, rotation, and flipX, flipY I know what to do with, but what is srcX and srcY, along with the srcWidth and srcHeight for?
edit: I played around and figured out what the srcX,srcY and srcHeight,Width do. I can not figure out what originX,Y does, even though I'm guess it's the centerpoint of the image. Since I don't want to play around with this one anyway, should I leave it as 0,0?
What would be common uses for manipulating the centerpoint of images?
Answering main question.
srcX, srcY, srcWidth, srcHeight are values determine which part (rectangle) of source texture you want to draw. For example, your source image is 100x100 pixels of size. And you want to draw only 60x60 part in a middle of source image.
batch.draw(texture, x, y, 20, 20, 60, 60);
Answering your edited question.
Origin is a center point for rotation and scale transformations. So if you want to your sprite scales and rotates around it's center point you should set origin values so:
float originX = width * 0.5f;
float originY = height * 0.5f;
In case you don't care about rotation and scaling you may not specify this params (leave it 0).
And keep in mind, that origin is not determines image drawing position (this is most common mistake). It means that two next method calls are draw image at same position (forth and fifth params are originX and originY):
batch.draw(image, x, y, 0, 0, width, height, ...);
batch.draw(image, x, y, 50, 50, width, height, ...);
According to the documentation, the parameters are as defined:
srcX - the x-coordinate in texel space
srcY - the y-coordinate in texel space
srcWidth - the source with in texels
srcHeight - the source height in texels