Using OneMap Address Search in iOS App

In this tutorial, we are going to implement the address search feature using OneMap REST API. If you want to know more about OneMap, please visit to OneMap API Help page.

We will continue our tutorial from my post how to add OneMap to iOS App, in which I’ve shown how to use ArcGIS iOS SDK and adding OneMap base map and setting map extent. Complete the steps in it before attempting this tutorial.

We are going to call the Address Search REST API from OneMap form our iOS app. I’ve tried out the API call and explored the data returned from the service in previous post Exploring OneMap REST API with Swift Playgrounds.

Add Navigation Controller
First, we need to add Navigation controller to our View Controller. Go to Editor > Embed In > Navigation Controller. A Navigation Controller is added to our View Controller.

Embed in navigation

Add UISearchController And UITableViewController
We are going to use the UISearchController to allow users to key in address. The results will be shown in UITableViewController as the user type in the key words. Add UISearchController property and UITableViewController property in the View Controller.

var resultTableViewController = UITableViewController()
var searchController = UISearchController()

view raw
gistfile1.swift
hosted with ❤ by GitHub

In the viewDidLoad( ) method, add following setup code for UISearchController.

// UISearchController Setup
searchController = UISearchController(searchResultsController: resultTableViewController)
searchController.searchResultsUpdater = self
searchController.searchBar.searchBarStyle = UISearchBarStyle.Minimal
searchController.hidesNavigationBarDuringPresentation = false
navigationItem.titleView = searchController.searchBar
definesPresentationContext = true
resultTableViewController.tableView.dataSource = self
resultTableViewController.tableView.delegate = self
searchController.dimsBackgroundDuringPresentation = false
searchController.delegate = self
searchController.searchBar.delegate = self

view raw
gistfile1.swift
hosted with ❤ by GitHub

Next, add the necessary delegates to View Controller as follow:

// delegates for AGSMapViewLayer, UISearchController, UITableView
class ViewController: UIViewController, AGSMapViewLayerDelegate,
UISearchResultsUpdating, UITableViewDelegate,
UITableViewDataSource, UISearchControllerDelegate,
UISearchBarDelegate {

view raw
gistfile1.swift
hosted with ❤ by GitHub

Create a data structure to hold the address data
Create a structure named Place to hold address name, category, X and Y.

struct Place {
var placeName = ""
var Category = ""
var X = 0.0
var Y = 0.0
}

view raw
gistfile1.swift
hosted with ❤ by GitHub

In ViewController, declare an array of Places. This array will be our data model to store the search results.

var searchResults = [Place]()

view raw
gistfile1.swift
hosted with ❤ by GitHub

Implement UITableView delegate protocol methods 
There are three protocol methods we need to implement to adopt the UITableView delegate protocol. Add the following methods to ViewController Class.

// UITableView delegate protocol methods
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
searchController.searchBar.text = searchResults[indexPath.row].placeName
showSelectedSearchPlace(searchResults[indexPath.row])
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let rowData = searchResults[indexPath.row].placeName
let cell = UITableViewCell()
cell.textLabel?.text = rowData
return cell
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return searchResults.count
}

view raw
gistfile1.swift
hosted with ❤ by GitHub

Implement UISearchResultsUpdating delegate protocol method
UISearchResultsUpdating protocol requires to implement the following method. This allow us to update search results based on the information users enters in the search bar.

func updateSearchResultsForSearchController(searchController: UISearchController) {
getAddresses(searchController.searchBar.text)
}

view raw
gistfile1.swift
hosted with ❤ by GitHub

Next, we will implement the function to call OneMap REST API to search for the address.

// Call OneMap's Address Search API to search a location in Singapore
func getAddresses(keyWord: String ){
if keyWord != "" {
let keyWordEscaped = keyWord.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)
let urlString = "http://www.onemap.sg/API/services.svc/basicSearch?token=qo/s2TnSUmfLz+32CvLC4RMVkzEFYjxqyti1KhByvEacEdMWBpCuSSQ+IFRT84QjGPBCuz/cBom8PfSm3GjEsGc8PkdEEOEr&wc=SEARCHVAL%20LIKE%20%27\(keyWordEscaped!)$%27&otptFlds=CATEGORY&returnGeom=0&nohaxr=10"
session.dataTaskWithURL(NSURL(string: urlString)!, completionHandler: {
(taskData, taskResponse, taskError) -> Void in
var jsonReadError : NSError?
var jsonResult = NSJSONSerialization.JSONObjectWithData(taskData, options: NSJSONReadingOptions.MutableContainers, error: &jsonReadError) as NSDictionary
let resultsArray = jsonResult["SearchResults"] as NSArray
self.searchResults = []
for (index, result) in enumerate(resultsArray){
if index > 0 {
var place = Place()
if let placeName = result["SEARCHVAL"] as String? {
place.placeName = placeName
}
if let category = result["CATEGORY"] as String? {
place.Category = category
}
if let x = result["X"] as NSString? {
place.X = x.doubleValue
}
if let y = result["Y"] as NSString? {
place.Y = y.doubleValue
}
self.searchResults.append(place)
}
}
self.resultTableViewController.tableView.reloadData()
}).resume()
}
}

view raw
gistfile1.swift
hosted with ❤ by GitHub

Run the application
If everything goes well, a search bar will be displayed on the navigation controller title place.

searchbar

When the user type in the search keywords in the search bar, getAddresses function is called and a list of addresses started by the keyword is shown in the table view as suggestion.

Auto suggestion

Show the selected one on the map view
We need to implement one more function to show the search result when the user clicks on one of the suggested places. This function is called in tableView:didSelectRowAtIndexPath: delegate method.

func showSelectedSearchPlace(selectedPlace :Place){
// to close suggested list when the user selected one
searchController.active = false
searchController.searchBar.text = selectedPlace.placeName
// a red color symbol to mark the place
let myMarkerSymbol = AGSSimpleMarkerSymbol()
myMarkerSymbol.color = UIColor.redColor()
// a point representing x,y data from selctedPlace
let point = AGSPoint(x: selectedPlace.X, y: selectedPlace.Y, spatialReference: mapView.spatialReference)
// make the selectedPlace at the center of the map
mapView.centerAtPoint(point, animated: false)
// make a graphic with symbol and point to add to the map
var pointGraphic = AGSGraphic(geometry: point, symbol: myMarkerSymbol, attributes: nil)
// remove previously searched places
graphicLayer.removeAllGraphics()
// display the graphic on the map
graphicLayer.addGraphic(pointGraphic)
}

view raw
gistfile1.swift
hosted with ❤ by GitHub

showaddress

Above screen is shown when the user selects City Hall from suggested addresses. The project is available on github. Please leave comments if you have any questions and suggestion.

Exploring OneMap REST API in Swift Playgrounds

I had posted how to add OneMap into your iOS application in previous post. In this tutorial we will try one of the OneMap REST APIs in Swift Playgrounds and see the return results. Xcode Playgrounds is perfect for experimenting with new APIs interactively. We are going to use Address Search API from OneMap.

This allows searching of address data for a given search value.  This also allows support of commands such as AND, OR, including word (+), excluding word (-), and exact phrase search using quotation marks. It will return search results with X,Y coordinates of the searched location.  http://www.onemap.sg/api/help/

First create a new Playgrounds file in xcode. Then import XCPlayground module. After that call XCPSetExecutionShouldContinueIndefinitely and set it true so that execution will continue and we can see the return result from asynchronous REST API calls.

import XCPlayground
XCPSetExecutionShouldContinueIndefinitely(continueIndefinitely: true)

view raw
gistfile1.swift
hosted with ❤ by GitHub

Next, we are going to use NSURLSession to make the REST API call to OneMap service. I use the example URL available in their api help page.

After that we pass the url string to dataTaskWithURL function of the session object and implement a completion handler function to tackle the return result from async call.

session.dataTaskWithURL(NSURL(string: urlString)!, completionHandler: {
(taskData, taskResponse, taskError) -> Void in
var jsonReadError: NSError?
var jsonResult = NSJSONSerialization.JSONObjectWithData(taskData, options: NSJSONReadingOptions.MutableContainers, error: &jsonReadError) as NSDictionary
for (key, value) in jsonResult{
println("\(key) : \(value)")
}
let arr = jsonResult["SearchResults"] as NSArray
println("\(arr[0])")
println("\(arr[1])")
println("\(arr[2])")
println("\(arr[3])")
println("\(arr[4])")
let categ = arr[1]["CATEGORY"]
let searchval = arr[1]["SEARCHVAL"]
let xval = arr[1]["X"]
let yval = arr[1]["Y"]
println("\(jsonReadError)")
println("\(taskData)")
println("\(taskResponse)")
}).resume()

view raw
gistfile1.swift
hosted with ❤ by GitHub

We can convert the result JSON data using NSJSONSerialization.JSONObjectWithData method and cast it as NSDationary. And then we can write println statement on the elements of  NSDationary result. The values of the result will be shown interactively at the right column of Playground. We can change the parameter values in the url string and see the result changes interactively.

onemapplaygroundWe will use this API call in the next post, adding OneMap Search Function in iOS App.

Using OneMap in iOS map application

I am learning Swift, the new programming language developed by Apple for iOS and OSX application development, and ArcGIS iOS SDK. I am planning  to make a series of tutorials based on what I have learned progressively.

In this tutorial, I am going to show you how to use Singapore’s official map platform, OneMap (www.onemap.sg), in iOS 8 application.

OneMap is an integrated map system for government agencies to deliver location-based services and information. It is a multi-agency collaboration with many government agencies currently participating and contributing information. ~ http://www.onemap.sg/home

To use OneMap in our iOS app, we need to use ArcGIS iOS SDK from Esri.  First download the ArcGIS SDK for iOS from https://developers.arcgis.com/ios/. Follow the guide to install and setup the development environment. ArcGIS has a very comprehensive guide for installing and setting up the development environment.

To use OneMap, first you need to add a UIView to your storyboard. In the Identity Inspector, change the UIView class name to AGCMapView.

MapView

Connect the map view to the view controller by creating an outlet in in ViewController.swift file.

Outlet

Import the ArcGIS framework in view controller’s swift file to use ArcGIS SDK for ios.

// import ArcGIS framework
import UIKit
import ArcGIS

view raw
import_arcgis
hosted with ❤ by GitHub

In the viewDidLoad method of ViewController.swift file, create a tiled map layer using OneMap map service and add it to the map:

override func viewDidLoad() {
super.viewDidLoad()
// set OneMap base map url
let url = NSURL(string: "http://e1.onemap.sg/arcgis/rest/services/BASEMAP/MapServer")
// create a tiled map layer
let tiledLayer = AGSTiledMapServiceLayer(URL: url)
// add layer to the mapView UIView
self.mapView.addMapLayer(tiledLayer, withName: "Basemap Tiled Layer")
}

view raw
gistfile1.swift
hosted with ❤ by GitHub

Run the application and you will see the map application displaying onemap.sg base map in the map view.

Initial Run

Right now, our map displays the whole Singapore, but we want it to be focus on a particular area in Singapore when it is loaded. To do this we need to define the initial map extent to zoom in display. We need to pass the envelope geometry to the  zoomToEnvelope:animated: method to zoom to the area we are interested in. 

An envelope is defined by a pair of X-Y coordinates representing the lower-left and upper-right corners of a rectangular bounding-box.

To create an envelope geometry, we need X min, Y min, X max and Y max. How do we find out this values? Pretty easy. We can get the current map’s extent from mapView.visibleAreaEnvelope property. So we just have to zoom in to the area of interest and inspect the current mapView.visibleAreaEnvelope values and use them as the initial extent in viewDidLoad method.

Our View Controller must adapt to the  AGSMapViewLayerDelegate protocol and set itself as the delegate of mapView‘s layerDelegate.

class ViewController: UIViewController, AGSMapViewLayerDelegate {

view raw
gistfile1.swift
hosted with ❤ by GitHub

In the viewDidLoad method, add this line to set delegate. See the API reference documentation for the AGSMapViewLayerDelegate protocol to learn more on layerDelegate

self.mapView.layerDelegate = self

view raw
gistfile1.swift
hosted with ❤ by GitHub

Then implement mapViewDidLoad delegate method to add observer for mapView pan and zoom notifications.

func mapViewDidLoad(mapView:AGSMapView!){
NSNotificationCenter.defaultCenter().addObserver(self, selector: "responseToEventChanged:", name: AGSMapViewDidEndZoomingNotification, object: nil)
}
func responseToEventChanged(notification: NSNotification){
let theString = "xmin = \(mapView.visibleAreaEnvelope.xmin),\nymin = \(mapView.visibleAreaEnvelope.ymin),\nxmax = \(mapView.visibleAreaEnvelope.xmax),\nymax = \(mapView.visibleAreaEnvelope.ymax)"
println(theString);
}

view raw
gistfile1.swift
hosted with ❤ by GitHub

Run the application and zoom to the area you want to show on initial start up. As you zoom in to the area, you can see then envelope geometry data printed out in Xcode console. When you are satisfied with the view, copy the X min, Y min, X max and Y max from the console and use them as initial extent.

I choose Nanyang Polytechnic as my starting view and create an envelop geometry from it. Following is the complete code:

import UIKit
import ArcGIS
class ViewController: UIViewController, AGSMapViewLayerDelegate {
@IBOutlet weak var mapView: AGSMapView!
let xmin = 29495.9472786567
let ymin = 39801.9418330241
let xmax = 30037.5707551916
let ymax = 40765.3094566208
override func viewDidLoad() {
super.viewDidLoad()
// add base map
let url = NSURL(string: "http://e1.onemap.sg/arcgis/rest/services/BASEMAP/MapServer")
let tiledLayer = AGSTiledMapServiceLayer(URL: url)
self.mapView.addMapLayer(tiledLayer, withName: "Basemap Tiled Layer")
self.mapView.layerDelegate = self
let envelope = AGSEnvelope(xmin: xmin, ymin: ymin, xmax: xmax, ymax: ymax, spatialReference: self.mapView.spatialReference);
self.mapView.zoomToEnvelope(envelope, animated: false)
}
func mapViewDidLoad(mapView:AGSMapView!){
NSNotificationCenter.defaultCenter().addObserver(self, selector: "responseToEventChanged:", name: AGSMapViewDidEndZoomingNotification, object: nil)
}
func responseToEventChanged(notification: NSNotification){
let theString = "xmin = \(mapView.visibleAreaEnvelope.xmin),\nymin = \(mapView.visibleAreaEnvelope.ymin),\nxmax = \(mapView.visibleAreaEnvelope.xmax),\nymax = \(mapView.visibleAreaEnvelope.ymax)"
println(theString);
}
}

view raw
gistfile1.swift
hosted with ❤ by GitHub

Mat on NYP

In this tutorial, we have learned how to add OneMap as based map to the iOS application and setting up initial map extent to the location of interest by using envelop geometry. If you want to know more about OneMap features and services, please visit to their API help page http://www.onemap.sg/api/help/.

Look forward for more tutorials on using OneMap from me and please leave a comment if you find any error or have difficulty following the steps above.

Batch Geocoding Tool For Singapore SVY21 Coordinate System

I have created a tool to geocode large amount Singapore postal codes into X, Y values of SVY21 coordinate system. SVY21 is the Singapore standard coordinate system used by Singapore Land Authority.

You can get the X, Y values of an address by using search function provided in www.onemap.sg. But if you have a few thousands of addresses to geocode, my batch geocoding tool can help you better. You need internet connection to use the tool because the program is the wrapper written in c# to call web service provided by onemap.

Download link:  Batch Geocoder Installer 

Download the installer and unzip the folder. Run setup to install the tool on your computer. You may need .NET runtime to run the program. After installation run the tool from start menu.

batch-geocode

Simply provide an input file containing a column of Singapore postal codes. Give the name and location of the output file where the result (X, Y) values are to be written. Click Run button to start the batch process. It may take a while depending on the number of postal codes you are processing.

Right now, the tool can only accept postal codes as input. In the future, I am planning to extend it to accept whole or partial addresses as input. The source code is hosted on Github https://github.com/zkkmin/BatchGeoCodeOneMap for anyone who is interested to look at it.

Focus productivity with Kanbanflow

We are getting more and more busy each day without accomplishing much. When we are not busy, we get distracted by other people, devices and even cats on the internet. We end up feeling exhausted at the end of the day without getting nearer to our life long goals, without finishing off our side projects and without writing as much as we should. We only have 24 hours a day and It is important that we spend them on the tasks that really matter. There are many productivity tools out there which can help you get more things done, but I found the tool call  Kanbanflow to be simple and very effective.

Kanbanflow combines the ideas of Pomodoro™  technique and Kanban board. It helps me visualize the important tasks I need to carry out during the week and focus on each tasks by using Pomodoro™ technique. The default Kanbanflow board has 4 columns named __ To-do, Do Today, In Progress and Done. You can add or remove columns as you like. Kanban board helps you manage the tasks visually. Built in Pomodoro™ timer allows you to focus on current task and track the time spending on it. Your productivity can be measured not only by the tasks you have completed but also by how many Pomodoro™ periods you have done for the day. You can also review how long did it take for a task to finish.

The simplest way to use Kanbanflow is to create a list of important tasks in To-do column. Each morning, plan your day by assigning the tasks from To-do column into Do today column. Move the task you are going to do next to In progress column. Use Pomodoro™ timer to work on them. When you finish a task, move it into Done column. If you like to plan for the week, add columns for each day of the week and put tasks in there.

I am a very happy user of this tool now and I can plan and execute my important tasks better than before. Do give it a try if you haven’t done so.

kanbanflow

CrushPlan Saved The Day

Today when I turned on my Z800 Workstation PC at work, it won’t start. There was no beeps, no display and no warning, just a blank screen staring at me while the power was on and the fans were running inside. I tried turning on and off the power a few times with no luck.

There are regular data processing tasks I need to run for the day and they can’t wait until the pc is repaired. Usually it takes a few weeks just to repair a pc in my company. It seemed like I am in deep shit and it’s only Monday!  Instead of panicking, I simply logged in to another computer in the office and restored the data required for the project I am working on. It took about 5 minutes to restore and I started working on my scheduled tasks for the day.

This is all possible thanks to CrashPlan from code42. It is a great backup software with very useful features. It is very easy to install, setup and configure. Please go to the product web page https://www.code42.com/crashplan/ for more details.

The best feature I like and which saved me big time today is the ability to back up to your friends/coworkers computers. They provide paid service – cloud back up plan for unlimited storage space. I just use free feature which allows me to backup to another computer. And it worked great and left me with very satisfying experience. I would recommend this hassle free software if you are not already using anything yet.

“Could not load data from the data source” error in ArcMap 10.1

When working in ArcMap, one would encounter all kinds of wired-ass problems for all kinds of unknown reasons. One of them is when you are working on a map document for a while, and out of the blue, unable to open the attribute table of a layer or layers in the document.

The error usually occurs when you right click on a layer and click “Open attribute table”. Instead of showing the attribute table, ArcMap displays you the following message and an empty attribute table. The strange thing is you still can select the rows in the attribute table and you can see how many rows in the table as well. It is just that all the rows are now empty.

couldnotload

empty_table

There are a few ways to solve this problem. One easy way, which worked for me, is to export out the problematic layer to a new geodatabase and then import it back to your map document. I hope this is helpful to those who encounters similar problem using ArcMap.

One or more layers failed to draw: ArcMap Drawing Errors

I have been working with a huge map document with thousands of layers, and encountered this weird error where by if I tried to zoom out, one of the layers disappeared. If I zoomed in, it appeared again.

I have tried many solutions I could google out for a few days, but nothing worked. I put my problem on geonet.esri.com discussion board and waited for the help. To my surprise, one user responded my question with a few precise suggestions and my problem has solved. The solution which worked for me is simply to copy the feature layers over to new map session.

Following is the link to the thread on geonet.esri.com where I got the help. 🙂
https://geonet.esri.com/message/451155#451155

SSH to EC2 instance from behind firewall

Well, this is a short post for those who would like to start an EC2 instance with different SSH ports to access from behind a corporate firewall.

First you must download and install CLI tools from Amazon to manage EC2 resources. After installing CLI tools, create a user data script as follow_

#!/bin/bash -ex
perl -pi -e 's/^#?Port 22$/Port 443/' /etc/ssh/sshd_config
service sshd restart || service ssh restart

After that, run ec2-run-instances command from command line as follow_

ec2-run-instances --key my-aws-keypair --region us-west-2 --instance-type t1.micro --user-data-file user-data-script-file.txt ami-70f96e40

You might want to change the above command with your own parameter set for keypair file, region, instance type etc. Please refer to manual for Amazon CLI tools for more details.

Go to Amazon web console to check your instance is getting started. Finally change security group to allow port 443 as inbound rule.

Now you can ssh into your ect instance with custom port.

Conversion between Singapore Coordinate System SVY21 and World Geodetic System WGS84

In August 2004, Singapore Land Authority introduced the new coordinated cadastre system, SVY21.

Recently, I need to use data from both onemap and google map, and the difference in coordinate systems made it pain in the ass to work with. So I wrote a Python script to convert to and from both coordinate systems.

Basically I used the geometry service provided by Esri Arcgis Online at ” http://tasks.arcgisonline.com/ArcGIS/rest/services/Geometry/GeometryServer

The script is hosted on https://github.com/zkkmin/coordconvert .

Since I wrote it mainly for my task requirement, you might need to modify it for your own usage 😀