22

I am building an app in which I have to highlight some countries dynamically in the world map. like this

enter image description here

In short I want to customize the whole view of ios maps as shown in the images. can this be done using MapKit or is there any other method. Thanks in advance

Rajesh
  • 937
  • 1
  • 8
  • 14

2 Answers2

9

You want to look into the Mapbox iOS SDK which will allow you to do this and more with a MapKit-like API. In particular, you will want to quickly make a custom map with TileMill using the provided Natural Earth data set for world country borders, enable UTFGrid interactivity so that the tapped regions can be identified, and use the RMShape class on an RMAnnotation onto the map view to add/color country polygons as needed. This sounds a little complex but the tools exist, are entirely free and open source, and I can help you with this process.

Pang
  • 9,564
  • 146
  • 81
  • 122
incanus
  • 5,100
  • 1
  • 13
  • 20
  • Justin I would really like to see an example of this, I have the same requirement and am working with vector images but this solution using MapBox would be epic! I followed you on twitter recently also, @colinpmasters – ColinMasters May 15 '15 at 13:42
  • 2
    @incanus I'm trying to figure this out in mapbox too. I see that Tilemill is now outdated, and I'm not having much luck. Any further pointers would be appreciated. – narco May 17 '16 at 20:57
0

I recently looked on some possibilities for implementing something similar and you could now actually implement something like this quite easily without additional libraries. To do that you need countries borders coordinates data and you can get that from Natural Earth (https://www.naturalearthdata.com/). This data is unfortunately in format that cannot be easily read on iOS but you can convert it to a json format or to be precise geojson format with QGIS (https://www.qgis.org/en/site/forusers/download.html) or you can just use geojson that someone else converted. Here is a geojson with countries coordinates: https://github.com/nvkelso/natural-earth-vector/blob/master/geojson/ne_110m_admin_0_countries.geojson that should fit the needs for the task in this question.

We can parse geojson file and draw a map with CAShapeLayers. Below is an example code that draws a map with random colors for shapes from geojson which gave me the result as on the screenshot below which I think is quite close to expected result so modifying it should be quite easy.

map_from_geojson

Geojson parsing and map drawing code example:

import UIKit
import CoreLocation

struct GeoJson: Codable {
    let type: String
    let features: [GeoJsonFeature]
}

struct GeoJsonFeature: Codable {
    let type: String
    let geometry: GeoJsonGeometry
}

struct GeoJsonGeometry: Codable {
    let type: String
    let coordinates: GeoJsonCoordinates
}

struct GeoJsonCoordinates: Codable {
    var point: [Double]?
    var line: [[Double]]?
    var polygon: [[[Double]]]?
    var multiPolygon: [[[[Double]]]]?

    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        if let point = try? container.decode([Double].self) {
            self.point = point
            return
        }
        if let line = try? container.decode([[Double]].self) {
            self.line = line
            return
        }
        if let polygon = try? container.decode([[[Double]]].self) {
            self.polygon = polygon
            return
        }
        if let multiPolygon = try? container.decode([[[[Double]]]].self) {
            self.multiPolygon = multiPolygon
            return
        }
        throw DecodingError.valueNotFound(Self.self, .init(codingPath: [], debugDescription: ""))
    }
}

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        loadGeoJson()
    }

    func loadGeoJson() {
        guard let url = Bundle.main.url(forResource: "ne_110m_admin_0_countries", withExtension: "geojson"),
              let data = try? Data(contentsOf: url),
              let geoJson = try? JSONDecoder().decode(GeoJson.self, from: data)
        else {
            return
        }
    
        for feature in geoJson.features {
            let geometry = feature.geometry
        
            let randomColor = UIColor(hue: Double.random(in: 0...1), saturation: 1, brightness: 1, alpha: 1)
        
            // check https://macwright.com/2015/03/23/geojson-second-bite.html for other types info if needed
            // note that below we do not support it exactly as it should (internal cutouts in polygons are ignored)
            // but for needed purpose it should not make a big difference
        
            if geometry.type == "Polygon", let coordinates = feature.geometry.coordinates.polygon {
                for polygon in coordinates {
                    addShape(polygon: polygon, color: randomColor)
                }
            }
            if geometry.type == "MultiPolygon", let coordinates = feature.geometry.coordinates.multiPolygon {
                for multiPolygon in coordinates {
                    for polygon in multiPolygon {
                        addShape(polygon: polygon, color: randomColor)
                    }
                }
            }
        }
    }

    func addShape(polygon: [[Double]], color: UIColor) {
        let polygonCoordinates: [CLLocationCoordinate2D] = polygon.map { coordinate in
            CLLocationCoordinate2D(latitude: coordinate[1], longitude: coordinate[0])
        }
        let points: [CGPoint] = polygonCoordinates.map { coordinate in
            coordinateToPoint(coordinate)
        }
        let path = UIBezierPath()
        path.move(to: points[0])
        for point in points {
            path.addLine(to: point)
        }
        path.close()
    
        let shapeLayer = CAShapeLayer()
        shapeLayer.path = path.cgPath
        shapeLayer.fillColor = color.cgColor
        shapeLayer.position = CGPoint(x: 50, y: 200)
    
        view.layer.addSublayer(shapeLayer)
    }

    func coordinateToPoint(_ coordinate: CLLocationCoordinate2D) -> CGPoint {
        let width = 300.0
        let height = 200.0
        let x = (coordinate.longitude + 180.0) * (width / 360.0)
        let latitudeRadians = coordinate.latitude * .pi / 180.0
        let n = log(tan((.pi / 4.0) + (latitudeRadians / 2.0)))
        let y = (height / 2.0) - (width * n / (2.0 * .pi))
        return CGPoint(x: x, y: y)
    }
}
Leszek Szary
  • 9,763
  • 4
  • 55
  • 62