LooLocator: Defining the Data Model


The model defines the way to encapsulate the data we need to show on the View. We'll use OpenStreetMaps API to get the list of amenities in the range. Before we do that, we need to take a quick detour to learn more about Overpass Turbo, our data provider.

Overpass Turbo

Overpass turbo is a web-based data filtering tool for OpenStreetMap. With overpass turbo we can run Overpass API queries and analyse the resulting OSM data interactively on a map. The queries can be written in either in Overpass QL or XML. We can specify if output needs to be in json or XML. Overpass Turbo also comes with a nice query builder that assists in building queries. The query we want to run posts the following XML to Overpass Turbo, and gets toilets in the range of 1000 meters.

<osm-script output="json">
  <query into="_" type="node">
    <has-kv k="amenity" modv="" v="toilets"/>
    <around from="_" into="_" 
    		lat="52.51633" lon="13.37751" radius="1000"/>
  </query>
  <print e="" from="_" geometry="skeleton" 
  		 limit="" mode="body" n="" order="id" s="" w=""/>
</osm-script>

The query translates to:

Find nodes which match the type amenity of type toilets, around a radius of 1000 of latitude 52.51633, and longitude 13.37751

We’ll now query the Overpass turbo API using curl.

curl -X POST -H 'Content-type: text/xml' \
-d '<osm-script output="json">
	<query into="_" type="node">
		<has-kv k="amenity" modv="" v="toilets"/>
		<around from="_" into="_" lat="52.51633" lon="13.37751" radius="800"/>
	</query>
	<print e="" from="_" geometry="skeleton" limit="" mode="body" n="" order="id" s="" w=""/>
	</osm-script>' \
https://overpass-api.de/api/interpreter

Tip: You can also test the query in Overpass Turbo visual query builder.

Overpass Turbo will process the query respond wit the nearest amenities as JSON data.

{
  "version": 0.6,
  "generator": "Overpass API 0.7.54.13 ff15392f",
  "osm3s": {
    "timestamp_osm_base": "2018-03-05T12:39:03Z",
    "copyright": "The data included in this document is from www.openstreetmap.org. The data is made available under ODbL."
  },
  "elements": [
	{
		"type": "node",
		"id": 927092067,
		"lat": 52.5141833,
		"lon": 13.3914532,
		"tags": {
			"amenity": "toilets",
			"fee": "yes",
			"name": "City Toilette",
			"toilets:wheelchair": "yes",
			"wheelchair": "yes",
		}
	},
	// more elements...
}

We’ll use intermediate DTOs that match the data from OSM, and convert it to the final location model.

internal struct RawOSMData: Codable {
    // We're interested only in elements, and can ignore other data
    var elements: [OSMElement]?
    private enum CodingKeys: String, CodingKey {
        case elements
    }
}

internal struct OSMElement: Codable {
    var type: String?
    var id: Int?
    var lat: Double?
    var lon: Double?
    var tags: Tags?
    struct Tags: Codable {
        var amenity: String?
        var fee: String?
        var name: String?
        var englishName: String?
        var wheelchair: String?
        enum CodingKeys: String, CodingKey {
            case englishName = "name:en"	
            case amenity = "amenity"
            case fee = "fee"
            case name = "name"
            case wheelchair = "wheelchair"
        }
    }
}

Location Model

And now we can define the Location Model class, which will have the following properties:

class Location: NSObject {
    let id: String
    let title: String?
    let locationDescription: String
    let coordinates: (Double, Double)
    var amenity: String?
    var fee: Bool?
    var feeAmount: String?
    let isAccessible: Bool
    
    init(id: String, title:String, locationDescription: String, coordintes: (Double, Double), isAccessible: Bool ) {
        self.id = id
        self.title = title
        self.locationDescription = locationDescription
        self.coordinates = coordintes
        self.isAccessible = isAccessible
    }
    convenience init(jsonElement: OSMElement) {
        self.init(id: String(describing: jsonElement.id),
                  title: jsonElement.tags?.englishName ?? jsonElement.tags?.name ?? "Unknown Amenity",
                  locationDescription: jsonElement.tags?.name ?? "Toilet",
                  coordintes: (jsonElement.lat ?? 0.0, jsonElement.lon ?? 0.0),
                  isAccessible: jsonElement.tags?.wheelchair != nil)
    }
}

The class Location has all the information we need to show the amenity on the MapView.

In the next post we’ll see how to design a modular networking layer for the App.