How to draw tiles on Map from Database? - ios

I am developing a GIS based iOS application using Swift 3.0. I want to draw tiles on Map and tiles images are stored in SQLite Database.
My question is how can I retrieve tile image from database and draw thoses image on Map, as database contains columns zoom (values will be 12, 13, etc.), tile_row (values will be 4122, 3413, etc.), tile_column (values will be 4122, 3413, etc.) and data but I get zoom level value in thousands and I get latitude and longitude values in iOS app, so I need to convert these values to match values in database. I found a way to convert zoom level to 1 to 18 scale but I don't know can I match the tile_row and tile_column value using latitude and longitude.
Also please verify that my code which convert zoom level to 1 to 18 (similar to google map zoom level) is correct:
let zoomLevel = Int(log2(360 / MKCoordinateRegionForMapRect(mapRect).span.longitudeDelta))
Thanks.

In IOS (and most mapping systems) the tile images are stored in a directory structure where each zoom level is a parent directory named with the zoom-level number. Under that directory, there are one or more directories named with the longitudinal tile numbers of the tiles below - e.g., at zoom level 10 there are 1024 tiles across and your directories could be 750, 751, 752, and 753 if that's where their images fell relatively. Under each longitude (x-coordinate) directory are the images (256 X 256 pixels) for that y-coordinate, each named for the x tile coordinate, again out of 1024 at zoom level 10.
To find where you are in those ranges, use MKMapPointForCoordinate(CLLocationCoordinate2D), which will give you the lat (y) and lon (x) map points of a location when fully zoomed out. To get the longitude (y) tile number use:
Int((pow(2.0, Double(z)) as Double) * point.y / 268435456.0)
...where the big number is the total number of points on the x-axis at zoom level 0 (2^20 tiles * 256 pixels / tile). That way if point.x is 1/3 of the big number, the image is the tile 1/3 of the way through 1024, and the integer representing the 256-pixel interval (i.e., tile) that number falls into is the name of the directory.
The latitude (y) map point and tile number are calculated similarly, and that number is the name of the image file. So, at zoom level 10 the image for the tile 752 out of 1024 along the x-axis and 486 out of 1024 along the y-axis would be in the file:
...Documents/Maps/yourDirectory/10/752/486.png
...provided you name your overall map directory Maps and the specific directory for this set of tiles yourDirectory. When you use the overlay, you'd use this directory information along with the rest of the setup to instantiate an MKTileOverlay object. Note that the offsets are from the bottom left corner unless you specify that they're reversed since they're thinking x and y axes (remind you of using CoreGraphics for a UIImage?).
Finally, here's how I calculate the zoom level given two corner points of a region that I want to capture a snapshot for:
let position1 = MKMapPointForCoordinate(bottomRight)
let position2 = MKMapPointForCoordinate(topLeft)
let xPosition1 = position1.x / Setting.shared.mapScale
let xPosition2 = position2.x / Setting.shared.mapScale
let yPosition1 = position1.y / Setting.shared.mapScale
let yPosition2 = position2.y / Setting.shared.mapScale
let relativeSpanX = xPosition1 - xPosition2 // X distance between points relative to size of full map
let relativeSpanY = yPosition1 - yPosition2 // Y distance between points relative to size of full map
let spanForZoom = max(relativeSpanX, relativeSpanY)
startingZoom = max(10, Int(log2(1.0 / spanForZoom)) - 1)
That gets you the zoom level for a tile that fits the size of the area that includes both points, but note that no one standard tile of that size (or any size less than the full map) may include those two points depending on where they lie relative to the grid. For example if they span the prime meridian the first step to 4 tiles at zoom level 1 will separate them, so you may need 2 - 4 tiles of the starting zoom size to get both. Ideally, write a function that tells you the tile number (x and y) that includes a CLLocationCoordinate2D since that gets very handy as you pick, download, and collect your tiles.
While you can get away with a geometrical approach like you're showing to calculate longitudinal / y-axis values, MKMapPointForCoordinate() is indispensable for latitude / y-axis calculations since the mercator map is non-linear as you move north or south, and the function takes care of that for you.
That should get you started, but it's a picky process - one thing to focus on is the fact that the layout is always from the lower left; it's easy to get confused as you gather and label the tiles.
I use the following function to calculate the x and y coordinates for the tile that a point is in for a given zoom level:
func getTileCoordinates(location: CLLocationCoordinate2D, z: Int) -> (x: Int, y: Int)
{
let point = MKMapPointForCoordinate(location)
let locationX = Int((pow(2.0, Double(z)) as Double) * point.x / 268435456.0)
let locationY = Int((pow(2.0, Double(z)) as Double) * point.y / 268435456.0)
return (locationX, locationY)
}
...again, where 268435456.0 is the total number of pixels in the zoom level 20 map along the x or y axis. Note, all of this is for Apples MapKit maps and the functions to display them.

Related

Highcharts Vector Plot with connected vectors of absolute length

Scenario: I need to draw a plot that has a background image. Based on the information on that image there have to be multiple origins (let's call them 'targets') that can move over time. The movements of these targets will have to be indicated by arrows/vectors where the first vector originates at the location of the target, the second vector originates where the previous vector ended and so on.
The result should look similar to this:
Plot with targets and movement vectors
While trying to implement this, i stumbled upon different questions:
I would use a chart with combined series: a Scatter plot to add the targets at exact x/y locations and a vector plot to insert the vectors. Would this be a correct way?
Since i want to set each vectors starting point to exact x/y coordinates i use rotationOrigin: 'start'. When i now change vectorLength to something other than 20 the vector is still shifted by 10 pixels (http://jsfiddle.net/Chop_Suey/cx35ptrh/) this looks like a bug to me. Can it be fixed or is there a workaround?
When i define a vector it looks like [x, y, length, direction]. But length is a relative unit that is calculated with some magic relative to the longest vector which is 20 (pixels) by default or whatever i set vectorLength to. Thus, the vectors are not connected and the space between them changes depending on plot size axes min/max). I actually want to corellate the length with the plot axis (which might be tricky since the x-axis and y-axis might have different scales). A workaround could be to add a redraw event and recalculate the vectors on every resize and set the vectorLength to the currently longest vector (which again can be calculated to correlate to the axes). This is very cumbersome and i would prefer to be able to set the vectors somehow like [x1, y1, x2, y2] where (x1/y2) denotes the starting- and (x2/y2) the ending-point of the vector. Is this possible somehow? any recommendations?
Since the background image is not just a decoration but relevant for the displayed data to make sense it should change when i zoom in. Is it possible to 'lock' the background image to the original plot min/max so that when i zoom in, the background image is also zoomed (image quality does not matter)?
Combining these two series shoudn't be problematic at all, and that will be the correct way, but it is necessary to change the prototype functions a bit for that the vectors will draw in a different way. Here is the example: https://jsfiddle.net/6vkjspoc/
There is probably the bug in this module and we will report it as new issue as soon as it is possible. However, we made the workaround (or fix) for that and now it's working well, what you can notice in example above.
Vector length is currently calculated using scale, namely - if vectorLength value is equal to 100 (for example), and vector series has two points which looks like that:
{
type: 'vector',
vectorLength: 100,
rotationOrigin: 'start',
data: [
[1, 50000, 1, 120],
[1, 50000, 2, -120]
]
}
Then the highest length of all points is taken and basing on it the scale is calculated for each point, so first one length is equal to 50, because the algorithm is point.length / lengthMax, what you can deduce from the code below:
H.seriesTypes.vector.prototype.arrow = function(point) {
var path,
fraction = point.length / this.lengthMax,
u = fraction * this.options.vectorLength / 20,
o = {
start: 10 * u,
center: 0,
end: -10 * u
}[this.options.rotationOrigin] || 0;
// The stem and the arrow head. Draw the arrow first with rotation 0,
// which is the arrow pointing down (vector from north to south).
path = [
'M', 0, 7 * u + o, // base of arrow
'L', -1.5 * u, 7 * u + o,
0, 10 * u + o,
1.5 * u, 7 * u + o,
0, 7 * u + o,
0, -10 * u + o // top
];
return path;
}
Regarding your question about defining start and end of a vector by two x, y values, you need to refactor entire series code, so that it won't use the vectorLength at all as like as scale, because you will define the points length. I suspect that will be very complex solution, so you can try to do it by yourself, and let me know about the results.
In order to make it works, you need to recalculate and update vectorLength of your vector series inside of chart.events.selection handler. Here is the example: https://jsfiddle.net/nh7b6qx9/

How do I geolocate an image returned by the 'map image' API?

The HERE Maps (the old Nokia Maps) has a variety of APIs, the most useful of which for non-web desktop applications looks like the Map Image API. This API allows you to specify a center location and a zoom level to get an image, along with an image size.
How do you georeference the returned image? The image is specified by center and zoom level, not by a bounding box or corner coordinates. If you need to display the map image as the background to other, geolocated data, how do find the coordinates of the image corners in order to render the map correctly?
My guess is that you can do so by using the zoom level, so, for example, a square image of any pixel size at a specific zoom level would have specific real-world dimensions. However, this is only a guess. The API documentation doesn't seem to have any content addressing georeferencing the map results at all.
The easiest way to do this would be to use the nomrk parameter and add two hidden poi markers for the top-left and bottom-right corners - as in this example of the Strait of Dover across the English Channel. [50N,2E - 51N,3E]
Ideally since the maps use the normalized Mercator projection, you should specify h and w to be the same value and keep the number of degrees latitude requested = number of degrees longitude requested. This will ensure that the POIs are in the corners of the map.
As an alternative, there is also the Map Tile API (log-in required), which uses standard Tile Map Service (TMS) addressing techniques. You can calculate the required tile using the following (sample code is in Java)
public void calculatePosition(int latitude, int longitude, int zoom) {
int p= Math.pow(2, zoom); double x= longitude /Math.PI; x=(x+1)/2;
double y= latitude/Math.PI; y = (log(tan(pi/4.0+latitude/2.0))+1)/2; y = 1 - y;
int column=(int) x*p; int row=(int)y*p;
System.out.println("[zoom,col,row] = " + zoom + "," + col+ "," + row);
}

geolocation calculation: calculate points if they are within a radius

ok here is the problem:
I want to show to my users the closest geopoints in his vicinity.
i have arround 55.000 fixed geopoints which could be shown. (I have for all of them long/lat)
dependent on the users location (long/lat) i want to show the closest 20 geopoints.
so of course i could just calculate all 55.000 geopoints and see if there are 20 within a certain radius of the users lang/lat and rank them afterwards but this would be somehow an overhead to calculate always 55tsd geopoints.
is there a posibility to "add" 5-miles/kilometers range to a users long and lat and calculate only the distance for those which are within this radius?
i hope you get what i mean. :)
Merci
If the calculation of distance between all points take too much time, you could narrow down the possible points just by simple difference between lat/lon (source_lat - target_lat) (source_lon - target_lon) and take only the closest point to final calculation of distance. You could use formula to calculate distance.
D = 60* 1.1515 * acos (sin(pi*y/180) * sin(pi*Y/180) +
cos(pi*y/180) * cos(pi*Y/180) * cos((z-Z) *pi / 180)
) * 180 / pi)
But the efficiency of calculation depends on used database technology as well.

How to create Random Geo-Points within a distance d from another Geo-point?

How to get Random Geo-points[ lat/long in decimal], placed anywhere inside a 100 meter radius circle? The center of the circle is another reference GeoPoint.
Is there any Function/Formulae that implements this?
Basically I am reading the GPS input of my android device and need to generate random Geo-Points around the device [In a circle of radius 100 meters centered at my device].
Please note : There are no Geo-Points pre-stored in Database. I need to create all the Geo-points on the fly as mentioned above.
I just wrote a a Ruby method which extends Random to provide this.
Caveat: The points all lay within a box, not a circle.
class Random
def location(lat, lng, max_dist_meters)
This is called with Random.new.location(mid_lat, mid_lng, dist). It will return a point which is probably within max_dist_meters of a the mid point.
max_radius = Math.sqrt((max_dist_meters ** 2) / 2.0)
Without adjusting to max_radius we'd get points inside a square outside the circle (i.e. in the corners), where the distance would be greater than max_dist_meters. This constrains us to a square inside the circle which is probably more what you want.
lat_offset = rand(10 ** (Math.log10(max_radius / 1.11)-5))
lng_offset = rand(10 ** (Math.log10(max_radius / 1.11)-5))
The 1.11 and 5 come from here.
lat += [1,-1].sample * lat_offset
lng += [1,-1].sample * lng_offset
lat = [[-90, lat].max, 90].min
lng = [[-180, lng].max, 180].min
We should probably wrap around here instead of just clamping the value, fixes welcome.
[lat, lng]
end
end
Comments / clean up welcome!
Sample output here which you can see nicely if you paste the lat/lngs here.
Pick random points on a square (i.e. pairs of uniform random numbers), then discard any that don't lie within a circle inscribed in that square. Given (x,y) pairs, a point is within your circle if:
(x - c_x)^2 + (y - c_y)^2 < r,
where (c_x, c_y) is the centre of your circle and r is its radius.
Start here: Generate a random point within a circle (uniformly). Then figure out how to center that circle at the reference lat/long. Then figure out how to map the randomly generated points to lat/long values. Hint: you want to add the individual components (say, x and y on your circle) to the circle's center.

Determine center of bins when using meshm

I am using meshm to plot densities.
How do I calculate the center point for each of the bins? I want to associate the data in m with the centerpoints I have looked at the source code of meshm and believe the solution will be a modified version of meshm that returns the latitude and longitudes of each bin's center point.
meshm calls the function meshgrat which returns the latitude and longitude points of the mesh that will be plotted by surfacem. The problem is that the lat and lon matrices are not the same size as m. I need to match the latitude and longitude points with the density data in m I believe I need to scale the data based on GRATSIZE a variable in meshgrat.
NOTE: meshm is part of the Matlab Mapping Toolbox
NOTE NOTE: This is a follow-up question to Determine distance from coastline in Matlab
You can get the edges of the mesh using MESHGRAT, which is the function called by meshm when it makes the grid for binning.
%# create the mesh that is used by meshm
[latgrat,longrat] = meshgrat(m,termaplegend);
%# find the latitudes of the center points
latPts = (latgrat(1:end-1,1:end-1) + latgrat(2:end,1:end-1))/2;
%# find the longitudes of the center points
lonPts = (longrat(1:end-1,1:end-1) + longrat(1:end-1,2:end))/2;
The center of the bin in 2nd row, 5th col is [latPts(2,5),lonPts(2,5)].
Within meshm just modify the inputs to meshgrat:
[lat,lon] = meshgrat(Z, R, gratsize); % original
[lat, lon] = meshgrat(Z,R); % modification
By default, gratsize = [] which in meshgrat will return the default graticule size of 50 X 100. By not passing in gratsize, the graticule is set to the same size as Z.

Resources