Being a city-state, Singapore has developed a very comprehensive 6 digit postal code system and you can locate almost any places in Singapore by using the magical 6 digits. Building on it, the Singapore government took the digitalization initiatives to gather many useful day-to-day information and services contributed by various government agencies like ACRA, HDB, URA, etc. The result from this digital integration is the birth of OneMap.
Unlike Google Maps which is managed and updated by a commercial company, OneMap is the authoritative national map of Singapore with the most detailed and timely updated information developed by the Singapore Land Authority. It provides location-based services for public to find out government related information, like bus explorer, landquery, schoolquery, trafficquery, etc. Here we will just use the most basic Search API to build an address search function with Power Apps and Power Automate. If you are interested in other things it can achieve, you can check out the complete API documentation.
Visualize the Function
What we are going to build is a Singapore address search function in Power Apps, with any part of the location as the search key word, like below.
Location name as key word
Postal code as key word
Part of address as key word
Understand the API
Because we are using the OneMap API, it is important to understand what is the required input we need to feed it and what is the expected output you can get. Based on the explanation on the basic search API, we need to provide 4 values as the parameters, namely searchVal, returnGeom, getAddrDetails and pageNum. pageNum is an optional parameter. Note it states that each response will be restricted to a maximum of 10 results.
Below is a sample response for each API call, which is in JSON format (JavaScript Object Notation). Besides the body of results, it also returns the totalNumPages. Together with pageNum, this makes it possible to extract all results with a tweak in the algorithm. We will see how to do it later.
Build the Power Automate Flow
Since we are going to get the search key word from Power Apps, create an Instant Cloud Flow and select PowerApps as the trigger.
Create a new step, search and select Initialize variable as the action.
Rename the action to “Get searchVal“, put “searchVal” as the name, select “String” as the type and select “Ask in PowerApps” from dynamic content as the value.
Create a new step, search and select HTTP as the action.
Select “GET” as the method, put “https://developers.onemap.sg/commonapi/search” as URI, under queries section, input the below three pairs of parameters. Put parameter title “searchVal” and select “searchVal” under variables section of dynamic content as the parameter value. Similarly key in the next two pairs of parameters, “returnGeom” – “Y” and “getAddrDetails” – “Y“.
Create a new step, search and select Parse JSON as the action.
Select “Body” under HTTP section in dynamic content as the content. Copy below code into the schema.
{
"type": "object",
"properties": {
"found": {
"type": "integer"
},
"totalNumPages": {
"type": "integer"
},
"pageNum": {
"type": "integer"
},
"results": {
"type": "array",
"items": {
"type": "object",
"properties": {
"SEARCHVAL": {
"type": "string"
},
"BLK_NO": {
"type": "string"
},
"ROAD_NAME": {
"type": "string"
},
"BUILDING": {
"type": "string"
},
"ADDRESS": {
"type": "string"
},
"POSTAL": {
"type": "string"
},
"X": {
"type": "string"
},
"Y": {
"type": "string"
},
"LATITUDE": {
"type": "string"
},
"LONGITUDE": {
"type": "string"
},
"LONGTITUDE": {
"type": "string"
}
},
"required": [
"SEARCHVAL",
"BLK_NO",
"ROAD_NAME",
"BUILDING",
"ADDRESS",
"POSTAL",
"X",
"Y",
"LATITUDE",
"LONGITUDE",
"LONGTITUDE"
]
}
}
}
}
Create a new step, search and select Initialize variable as the action. Rename the action to “Initialize pageNum“, put “pageNum” as the name, select “Integer” as the type and put “1” as the value.
Create a new step, search and select Initialize variable as the action. Rename the action to “Initialize resultString“, put “resultString” as the name, select “String” as the type and leave value field blank. This will be used to hold search result.
Create a new step, search and select Do until as the action.
Select “pageNum” under variables section of dynamic content as object to be compared, select “is greater than” as the condition, and select “totalNumPages” under Parse JSON section as the condition value.
Within the Do until block, create a new step and select HTTP as the action. Rename the action to “Get response for each result page” and follow the previous HTTP action to fill up method, URI and 3 pairs of parameters. But this time we add the last pair of parameters, put parameter title “pageNum” and select “pageNum” under variables section of dynamic content as the parameter value.
Still within the Do until block, create a new step and select Parse JSON as the action. Rename the action to “Parse response for each result page” and follow the previous Parse JSON action to copy the same code into the schema. For content field, select “Body” under ‘Get response for each result page’ section from dynamic content.
Still within the Do until block, create a new step. Search and select Apply to each as the action.
For the output to be looped, search ‘results’ in the dynamic content and select “results” under ‘Parse response for each result page” section.
Within the Apply to each block, create a new step. Search and select Append to string variable as the action.
Select resultString as the name. For value field, switch from dynamic content tab to Expression tab, and copy below code into the expression field and click OK.
concat(items('Apply_to_each')?['BUILDING'],'|',items('Apply_to_each')?['BLK_NO'],' ',items('Apply_to_each')?['ROAD_NAME'],'|',items('Apply_to_each')?['POSTAL'],'|',items('Apply_to_each')?['LATITUDE'],'|',items('Apply_to_each')?['LONGITUDE'],'@')
Outside the Apply to each block but still within the Do until block, create a new step. Search and select Increment variable as the action.
Select pageNum as the name and put 1 in the value field.
Add a new step completely outside the Do until block, search and select Condition as the action.
For the object to be compared, switch from dynamic content to Expression tab, and copy below code into the expression field and click OK. Select “is equal to” as the condition, and put 0 as the condition value.
length(variables('resultString'))
Within the ‘If no’ branch, add a new step, search and select Substring as the action.
For Text field, select “resultString” under variables section from dynamic content. Put 0 in the Starting position field.
For the Length field, switch from dynamic content to Expression tab, and copy below code into the expression field and click OK.
sub(length(variables('resultString')), 1)
Outside the Condition block, create a new step. Search and select “Respond to a PowerApp or flow” as the action.
Add a Text output and name it “searchResult“. For the value to respond, select “Substring” under ‘Substring’ section in dynamic content.
Now you can save the entire flow and we are done with the flow portion.
You may find it confusing when following the steps so below are some explanation.
1. The entire “Do until” block is to collect all pages of returned results from OneMap. If you just want to take the first page of results, move the “Apply to each” block out of “Do until” block and remove the rest of the “Do until” block.
2. Although flow can respond to a PowerApp or another flow, but the type of the output is restricted to Text, Yes/No, File, Email, Number or Date. It is not possible to return a JSON or array back to PowerApp. This is why we use resultString to transform the JSON output into a long string and pass it back to PowerApp. Later I will show you how to restore separate items from this long string in Power Apps.
3. In the “Append to string variable” action, we used concat function and two separators to transform JSON into long string, | (vertical pipe) and @ (at sign). This is because these two symbols seldom appear in an address. However you are free to use other symbols (like ^, !, etc)as long as you can effectively restore the result from this long string.
Decode in Power Apps
In the app where you want to use this flow, insert a Text Input object, and rename it as searchVal. Insert a button to trigger the search action, import the flow into Power Apps and attach it to this button. In the OnSelect property of this button, copy and paste below code. OneMapSearch is the name of the flow I have just built and you may need to replace it with your own flow name.
ClearCollect(searchResults, Split(OneMapSearch.Run(searchVal.Text).searchresult,"@"));
ClearCollect(sortedResults,
{
'Building Name': Blank(),
'Detailed Address': Blank(),
'Postal Code': Blank(),
Latitude: Blank(),
Longitude: Blank()
});
ForAll(searchResults,Collect(sortedResults,
{
'Building Name': First(Split(ThisRecord.Result, "|")).Result,
'Detailed Address': Last(FirstN(Split(ThisRecord.Result, "|"), 2)).Result,
'Postal Code': Last(FirstN(Split(ThisRecord.Result, "|"), 3)).Result,
Latitude: Value(Last(FirstN(Split(ThisRecord.Result, "|"), 4)).Result),
Longitude: Value(Last(Split(ThisRecord.Result, "|")).Result)
}));
RemoveIf(sortedResults, Or(IsBlank('Postal Code'), 'Postal Code' = "NIL"))
This will decode the long string returned by the flow and populate the information into a collection with proper columns. Now insert a gallery and put sortedResults as the data source to display the information.
Now you have successfully stored the search results from OneMap into the sortedResults collection and you can use the information based on your use cases.
Known Limitations
Apparently any address containing | or @ will not be shown correctly, but you should be able to spot it fairly easily from the output since it will definitely produce weird results.
There is a limit on how long Power Apps will wait for Power Automate to return a result. From my testing, it is about 2 minutes, after which the Power Apps will just time out and refuse to take any output from Power Automate. You will not receive any error message at Power Apps end but in Power Automate you will see an error message like below.
2 minutes should allow OneMap to return about 500 entries of addresses, enough for most of the use cases.