WMS service of IOS Gaode Map & mapbox map WMS service

Time:2020-9-18

Web map service (WMS)

Web map service (WMS) uses data with geographic location information to make maps. Map is defined as the visual representation of geographic data. It can return the corresponding map according to the user’s request (including PNG, GIF, JPEG and other grid forms, or SVG and web CGM and other vector forms). WMS supports the Network Protocol HTTP, and the operations supported are defined by URLs.

Let’s first analyze a wave of service links:
The general situation gives us the following: http://… …/geoserver/xf/wms ?service=WMS&version=1.1.0&request=GetMap&layers=zgwz_ lzyz_ f08&styles=&bbox=-1844.9489742784644,0.0,35699.99999999942,34437.74999999997&width=768&height=704&srs= EPSG:3857&format=application/openlayers

Parameters:

  • service=WMS
  • Version = 1.1.0: Version (1.1… And 1.3… Are different in calculation)
  • request=GetMap
  • layers=zgwz_ lzyz_ F08: layer array (if this parameter is wrong, it will not be displayed)
  • styles=
  • Bbox = – 1844.9489742784644,0.035699.999999994234437.7499999997: box (display area) generally needs calculation
  • width=768
  • height=704
  • srs= EPSG:3857 : coordinate type: 3857 and 900913 are pseudo Mercator projections, also known as spherical Mercator coordinates; 4326 is WGS84 longitude and latitude coordinates
  • Format = Application / openlayers: format, IOS uses this format = image / PNG
  • Transparent = true: transparent true: you can see the base map of Gaode, false, you can’t see the base map of Gaode
  •  ==>**http://… …/geoserver/xf/wms?SERVICE=WMS&VERSION=1.1.0&REQUEST=GetMap&LAYERS=zgwz_lzyz_f08_gd&TRANSPARENT=TRUE&STYLES=&WIDTH=762&HEIGHT=768&srs=EPSG:3857&FORMAT=image/png&BBOX=

 

1、 Map of Gaud:

Add WMS service to the map through matileoverlay interface

1. The custom class wmstileoverlay inherits from matileoverlay

import UIKit

class WMSTileOverlay: MATileOverlay {
    var rootURL = ""
    var titleSize = 0
    var initialResolution = 0.0
    var originShift = 0.0
    var HALF_PI = 0.0
    var RAD_PER_DEGREE = 0.0
    var HALF_RAD_PER_DEGREE = 0.0
    var METER_PER_DEGREE = 0.0
    var DEGREE_PER_METER = 0.0
    
    ///Initialization
    ///- parameter initrooturl: online map path & Transport = true & format = image / PNG & bbox=
    init?(rootURL initRootURL: String?) {
        super.init()
        rootURL = initRootURL ?? ""
        titleSize = 256
        initialResolution = 156543.03392804062////2*Math.PI*6378137/titleSize
        Originshift = 20037508.342789244 // half of perimeter 2* Math.PI *6378137/2.0
        HALF_PI = .pi / 2.0
        RAD_PER_DEGREE = .pi/180.0
        HALF_RAD_PER_DEGREE = .pi/360.0
        METER_ PER_ Degree = originshift / 180.0 // degree, how many meters
        DEGREE_ PER_ Meter = 180.0 / originshift // how many degrees is one meter
    }
    
    /**
     *@ brief generates the URL in tile path. This method is used to load tile, which is filled with urltemplate by default
     * @param path tile path
     *@ return generates tileoverlay with tile path
     */
    override func url(forTilePath path: MATileOverlayPath) -> URL {
        
        let strURL = "\(rootURL)\(titleBoundsBy(x: path.x, y: path.y, zoom: path.z) ?? "")"
        let url = URL(string: strURL)
        return url!
    }
    
    /**
     *@ brief loads the requested tile, and accesses the callback block with tile data or tile loading failure error. The default implementation is to first use urlfortilpath to get the URL, and then use asynchronous nsurlconnection to load tile
     * @param path tile path
     *@ param result is the callback block used to pass in tile data or to load error access of tile failure
     */
    override func loadTile(at path: MATileOverlayPath, result: @escaping(Data?, Error?) -> Void) {
        let url = self.url(forTilePath: path)
        let request = NSMutableURLRequest(url: url)
        request.httpMethod = "GET"
        let session = URLSession.shared
        session.dataTask(with: request as URLRequest, completionHandler: {(data, response, error) in
            if error != nil {
                #if DEBUG
                print("Error downloading tile")
                #endif
                result(nil, error)
            }
            else {
                result(data, nil)
            }
        }).resume()
    }
    
    ///Cancel the tile request. When the map display area changes, the tile download outside the display area will be canceled. When disableoffscreentileloading = yes, it will be called. since 5.3.0
    /// - Parameter path: path
    override func cancelLoadOfTile(at path: MATileOverlayPath) {
        super.cancelLoadOfTile(at: path)
    }
    
    /**
     *Returns the tile range according to the X / y level of the tile
     *
     * @param tx
     * @param ty
     * @param zoom
     * @return url
     */
    func titleBoundsBy(x: Int, y: Int, zoom: Int) -> String? {
        let minX = pixels2Meters(x * titleSize, zoom: zoom)
        let maxY = -pixels2Meters(y * titleSize, zoom: zoom)
        let maxX = pixels2Meters(((x + 1) * titleSize), zoom: zoom)
        let minY = -pixels2Meters(((y + 1) * titleSize), zoom: zoom)
        
        return "\(minX),\(minY),\(maxX),\(maxY)"
    }
    
    /**
     *The coordinates are calculated according to pixels and levels
     *
     * @param p p
     * @param zoom z
     * @return double
     */
    func pixels2Meters(_ p: Int, zoom: Int) -> Double {
        return Double(p) * resolution(zoom) - originShift
    }
    
    /**
     *Calculation resolution
     *
     * @param zoom z
     * @return double
     */
    func resolution(_ zoom: Int) -> Double {
        return initialResolution / (pow(2.0, Double(zoom)))
    }
}

2. Call mapcontroller in map controller

import UIKit
class MapController: UIViewController {
    let tileOverlay = WMSTileOverlay.init(rootURL: "http://... .../geoserver/haitu/wms?SERVICE=WMS&VERSION=1.1.0&REQUEST=GetMap&LAYERS=haitu:gis_t_landuse_a&TRANSPARENT=TRUE&STYLES=&srs=EPSG:3857&FORMAT=image/png&BBOX=")
    
    override func viewDidLoad() {
        super.viewDidLoad()
        let map = MAMapView(frame: frame: view.bounds)
        mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        mapView.setCenter(CLLocationCoordinate2D(latitude: 39.97000000, longitude: 116.3300000), zoomLevel: 20, animated: false)
        Tileoverlay?. disableoffscreentileploading = true // stops downloading tiles that are not in the display area
        mapView.add(tileOverlay)
        view.addSubview(mapView)
    }
}

 

3. Exception handling

(1) WMS service map has no coordinates: it will cause the layer can not be displayed in the correct position. At this time, the layer will be displayed in the (0, 0) coordinate position of the map (Africa). The method of troubleshooting is to set the map center at (0, 0) coordinate, and then zoom in. The solution is that the drawing engineer gives you a map with coordinates.

(2) Layers are superimposed many times: the first time this happens may be caused by our own caching of layers. Try deleting the cache.

(3) Grid: in this case, there may be problems with the published graph or bbox parameter calculation. There are many reasons for this situation. We welcome to add.

(4) Different coordinate systems, WMS version 1.1 and 1.3: if WMS serves 84 coordinate system, there will be an offset when it is superimposed on the gaude coordinate system. We can let the drawing engineer draw a graph of the gaude coordinate system, or we can do it in the code ourselves. There will be a bit of calculation. The code is as follows:

In step 1, change the method func titleboundsby (X: int, Y: int, zoom: int) – > string? In the custom class wmstileoverlay

/**
 *Returns the tile range according to the X / y level of the tile
 *
 * @param tx
 * @param ty
 * @param zoom
 * @return url
 */
func titleBoundsBy(x: Int, y: Int, zoom: Int) -> String? {
    let minX = pixels2Meters(x * titleSize, zoom: zoom)
    let maxY = -pixels2Meters(y * titleSize, zoom: zoom)
    let maxX = pixels2Meters(((x + 1) * titleSize), zoom: zoom)
    let minY = -pixels2Meters(((y + 1) * titleSize), zoom: zoom)
    
    return "\(minX),\(minY),\(maxX),\(maxY)"
}

 

Change to

/**
 *Returns the tile range according to the X / y level of the tile
 *
 * @param tx
 * @param ty
 * @param zoom
 * @return url
 */
func titleBoundsBy(x: Int, y: Int, zoom: Int) -> String? {
    var minX = pixels2Meters(x * titleSize, zoom: zoom)
    var maxY = -pixels2Meters(y * titleSize, zoom: zoom)
    var maxX = pixels2Meters(((x + 1) * titleSize), zoom: zoom)
    var minY = -pixels2Meters(((y + 1) * titleSize), zoom: zoom)
    
    //Convert to latitude and longitude
    minX = meters2Lon(minX)
    minY = meters2Lat(minY)
    maxX = meters2Lon(maxX)
    maxY = meters2Lat(maxY)
    
    //Convert the longitude and latitude of the target to gaude coordinate system.
    let amapcoord = AMapCoordinateConvert(CLLocationCoordinate2DMake(CLLocationDegrees(minY), CLLocationDegrees(minX)), type)
    minY = amapcoord.latitude
    minX = amapcoord.longitude
    let maxAmapcoord = AMapCoordinateConvert(CLLocationCoordinate2DMake(CLLocationDegrees(maxY), CLLocationDegrees(maxX)), type)
    maxY = maxAmapcoord.latitude
    maxX = maxAmapcoord.longitude
    
    //Convert to Mercator
    minX = lon2Meters(minX)
    minY = lat2Meters(minY)
    maxX = lon2Meters(maxX)
    maxY = lat2Meters(maxY)
    
    //Some blogs have mentioned that there is a difference between version 1.1 and version 1.3, which has not been tried before. If you meet, welcome to add
    result = "\(minX),\(minY),\(maxX),\(maxY)" //1.1
    //result = "\(minX),\(minY),\(maxX),\(maxY)"//1.3
    
    return result
}

//////Add the corresponding method of coordinate transformation
/**
 *X meter to longitude and latitude
 */
func meters2Lon(_ mx: Double) -> Double {
    let lon = mx * DEGREE_PER_METER
    return lon
}

/**
 *Y meter to longitude and latitude
 */
func meters2Lat(_ my: Double) -> Double {
    var lat = my * DEGREE_PER_METER
    lat = 180.0 / .pi * (2 * atan(exp(lat * RAD_PER_DEGREE)) - HALF_PI)
    return lat
}

/**
 *X longitude and latitude to meter
 */
func lon2Meters(_ lon: Double) -> Double {
    let mx = lon * METER_PER_DEGREE
    return mx
}

/**
 *Y longitude and latitude to meter
 */
func lat2Meters(_ lat: Double) -> Double {
    var my = log(tan((90 + lat) * HALF_RAD_PER_DEGREE))/(RAD_PER_DEGREE)
    my = my * METER_PER_DEGREE
    return my
}

 

2、 Mapbox map:

Suggestion: use mapbox for indoor map, because mapbox zoom level can reach 1 meter effect.

1. The WMS service of mapbox is the same as adding raster image, even we don’t need to modify it. The only thing we need to pay attention to is the parameter: bbox = {bbox-epsg-3857}

import UIKit
import Mapbox

class ViewController: UIViewController, MGLMapViewDelegate {
    var mapView: MGLMapView!
    var rasterLayer: MGLRasterStyleLayer?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        mapView = MGLMapView(frame: view.bounds)
        mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        
        mapView.setCenter(CLLocationCoordinate2D(latitude: 39.97000000, longitude: 116.3300000), zoomLevel: 20, animated: false)
        
        mapView.delegate = self
        
        view.addSubview(mapView)
        
        // Add a UISlider that will control the raster layer’s opacity.
        addSlider()
    }
    
    func mapView(_ mapView: MGLMapView, didFinishLoading style: MGLStyle) {
        // Add a new raster source and layer.
        let source = MGLRasterTileSource(identifier: "stamen-watercolor", tileURLTemplates: ["http://.../geoserver/xf/wms?service=WMS&version=1.1.0&request=GetMap&layers=zgwz_lzyz_f08_3857&styles=&bbox={bbox-epsg-3857}&width=768&height=704&srs=EPSG:3857&format=image/png"], options: [ .tileSize: 256 ])
        let rasterLayer = MGLRasterStyleLayer(identifier: "stamen-watercolor", source: source)
        
        style.addSource(source)
        style.addLayer(rasterLayer)
        
        self.rasterLayer = rasterLayer
    }
    
    @objc func updateLayerOpacity(_ sender: UISlider) {
        rasterLayer?.rasterOpacity = NSExpression(forConstantValue: sender.value as NSNumber)
    }
    
    func addSlider() {
        let padding: CGFloat = 10
        let slider = UISlider(frame: CGRect(x: padding, y: self.view.frame.size.height - 44 - 30, width: self.view.frame.size.width - padding * 2, height: 44))
        slider.minimumValue = 0
        slider.maximumValue = 1
        slider.value = 1
        slider.isContinuous = false
        slider.addTarget(self, action: #selector(updateLayerOpacity), for: .valueChanged)
        view.insertSubview(slider, aboveSubview: mapView)
        if #available(iOS 11.0, *) {
            let safeArea = view.safeAreaLayoutGuide
            slider.translatesAutoresizingMaskIntoConstraints = false
            let constraints = [
                slider.bottomAnchor.constraint(equalTo: safeArea.bottomAnchor, constant: -mapView.logoView.bounds.height - 10),
                slider.widthAnchor.constraint(equalToConstant: self.view.frame.size.width - padding * 2),
                slider.centerXAnchor.constraint(equalTo: safeArea.centerXAnchor)
            ]
            
            NSLayoutConstraint.activate(constraints)
        } else {
            slider.autoresizingMask = [.flexibleTopMargin, .flexibleLeftMargin, .flexibleRightMargin]
        }
    }
}