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 of1000
of latitude52.51633
, and longitude13.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.