Show Golf Courses in a Google Maps

S

In this tutorial you will learn to use Google Maps in your Android application. With the Maps SDK for Android, you can add maps based on Google Maps data to your application. The API automatically handles access to Google Maps servers, data downloading, map display, and response to map gestures.

Golf course JSON Data

Your target is to create Android application, which shows some of the Finnish Golf Courses in a Google Maps. You can use a following JSON file in your application https://ptm.fi/materials/golfcourses/golf_courses.json

{
    "info":"SGKY:N JÄSENKENTÄT 2016",
    "courses": [
        {
            "type":"Kulta",
            "lat":62.2653926,
            "lng":22.6415612,
            "course":"Alastaro Golf",
            "address":"Golfkentäntie 195, 32560 Virttaa",
            "phone":"(02) 724 7824",
            "email":"minna.nenonen@alastarogolf.fi",
            "web":"http://alastarogolf.fi/",
            "image":"kuvat/kulta.jpg",
            "text":"Alastaro Golfin Virttaankankaan..."
        },...

CREATE A NEW PROJECT

  • Start Android Studio
  • Select Google Maps Activity
  • Create a new project – GolfCoursesInAMap

Google Maps Activity template is used to create a Google Maps based applications in an Android Studio. You can also use MapView to include a Google Maps inside your application.

Android Studio starts Gradle and builds your project. This may take a few seconds. For more information about creating a project in Android Studio, see the Android Studio documentation. You should first follow Create a first Android application tutorial, if this is your first time to create an Android project.

Get a Google Maps API key

When the build is finished, Android Studio opens the google_maps_api.xml and the MapsActivity.java files in the editor. Notice that the google_maps_api.xml file contains instructions on getting a Google Maps API key before you try to run the application. You will get a Map without any map tiles, if you try to launch your application without a Google Maps API key.

Your application needs an API key to access the Google Maps servers. The type of key you need is an API key with restriction for Android apps. The key is free. You can use it with any of your applications that call the Maps SDK for Android, and it supports an unlimited number of users.

Copy the link provided in the google_maps_api.xml file and paste it into your browser. The link takes you to the Google Cloud Platform Console and supplies the required information to the Google Cloud Platform Console via URL parameters, thus reducing the manual input required from you.

Follow the instructions to create a new project on the Google Cloud Platform Console or select an existing project. Create an Android-restricted API key for your project. Copy the resulting API key, go back to Android Studio, and paste the API key into the <string> element in the google_maps_api.xml file.

TEST YOUR PROJECT BUILD

Run and test your project. Google Maps should be visible and it should be pointed to Sidney by default.

Take a look at the code

Examine the code supplied by the template. In particular, look at the following files in your Android Studio project.

The XML layout file

By default, the XML file that defines the app’s layout is at res/layout/activity_maps.xml. It contains the following code. Fragment based class will be used to display a Google Maps in your application. You can use map id to link your code to the map.

<fragment xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/map"
    tools:context=".MapsActivity"
    android:name="com.google.android.gms.maps.SupportMapFragment" />

The maps activity file

By default, the Kotlin file that defines the maps activity is named MapsActivity.kt. It should contain the following code after your package name. Code will use OnMapReadyCallback to call onMapReady function, when the Google Maps is initialized.

class MapsActivity : AppCompatActivity(), OnMapReadyCallback {

    private lateinit var mMap: GoogleMap

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_maps)
        val mapFragment = supportFragmentManager
            .findFragmentById(R.id.map) as SupportMapFragment
        mapFragment.getMapAsync(this)
    }

    override fun onMapReady(googleMap: GoogleMap) {
        mMap = googleMap

        // Add a marker in Sydney and move the camera
        val sydney = LatLng(-34.0, 151.0)
        mMap.addMarker(MarkerOptions().position(sydney).title("Marker in Sydney"))
        mMap.moveCamera(CameraUpdateFactory.newLatLng(sydney))
    }
}

A new marker will be create with LatLng function and it will point to Sidney. Camera will be moved to point to the same position.

Load Golf courses Json data

Open the build.gradle file from your app module and add a Volley library inside dependencies block. Volley is an HTTP library that makes networking for Android apps easier and most importantly, faster. Volley is available on GitHub.

implementation 'com.android.volley:volley:1.1.1'

Comment all the Sydney code in the onMapReady function. Create an own loadData function and call it from the onMapReady function.

private fun loadData() {
  val queue = Volley.newRequestQueue(this)
  // 1. code here
  // create JSON request object
  val jsonObjectRequest = JsonObjectRequest(
    Request.Method.GET, url, null,
    Response.Listener { response ->
      // JSON loaded successfully
      // 2. code here
    },
    Response.ErrorListener { error ->
      // Error loading JSON
    }
  )
  // Add the request to the RequestQueue
  queue.add(jsonObjectRequest)
  // ADD LATER custom info window adapter here 
}

Add a few variables (to 1. code here in above code) , which will be used to store loaded JSON data. All the golf courses will be stored to golf_courses array. Different golf course types, in this application, will use a different marker colors. course_types map will be used in this purpose.

val url = "https://ptm.fi/materials/golfcourses/golf_courses.json"
var golf_courses: JSONArray
var course_types: Map<String, Float> = mapOf(
  "?" to BitmapDescriptorFactory.HUE_VIOLET,
  "Etu" to BitmapDescriptorFactory.HUE_BLUE,
  "Kulta" to BitmapDescriptorFactory.HUE_GREEN,
  "Kulta/Etu" to BitmapDescriptorFactory.HUE_YELLOW
)

After JSON is loaded successfully (to 2. code here in above code), it can be parsed. Loop through all of the object in JSONArray. Get all course data and create a new Google Maps Marker. Pass data to marker via Tag. It will be used later with InfoWindowAdapter. After that, camera will be moved to point center of Finland.

golf_courses = response.getJSONArray("courses")
// loop through all objects
for (i in 0 until golf_courses.length()){
  // get course data
  val course = golf_courses.getJSONObject(i)
  val lat = course["lat"].toString().toDouble()
  val lng = course["lng"].toString().toDouble()
  val coord = LatLng(lat, lng)
  val type = course["type"].toString()
  val title = course["course"].toString()
  val address = course["address"].toString()
  val phone = course["phone"].toString()
  val email = course["email"].toString()
  val web_url = course["web"].toString()

  if (course_types.containsKey(type)){
    var m = mMap.addMarker(
      MarkerOptions()
        .position(coord)
        .title(title)
        .icon(BitmapDescriptorFactory
          .defaultMarker(course_types.getOrDefault(type, BitmapDescriptorFactory.HUE_RED)
         )
      )
   )
   // pass data to marker via Tag
   val list = listOf(address, phone, email, web_url)
   m.setTag(list)
 } else {
   Log.d(TAG, "This course type does not exist in evaluation ${type}")
 }
}
mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(LatLng(65.5, 26.0),5.0F))

TEST YOUR PROJECT

Run your project. Now a Golf course markers should be visible.

Custom INfo Window Adapter

An info window allows you to display information to the user when they tap on a marker. Only one info window is displayed at a time. If a user clicks on a marker, the current info window will be closed and the new info window will be displayed. Note that if the user clicks on a marker that is currently showing an info window, that info window closes and re-opens.

layout

Create a new layout file for the info window. This info window will show golf course name, address, phone, email and web page address.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/titleTextView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center_horizontal"
        android:textSize="16sp"
        android:textStyle="bold" />
    <TextView
        android:id="@+id/addressTextView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>
    <TextView
        android:id="@+id/phoneTextView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
    <TextView
        android:id="@+id/emailTextView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
    <TextView
        android:id="@+id/webTextView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
</LinearLayout>

adapter

Create a new inner CustomInfoWindowAdapter class to your MainActivity. Info Window layout will be loaded from info_window resource. All the data will be get via Marker. Title is a string and rest of the data is passed via tag as a list.

internal inner class CustomInfoWindowAdapter : GoogleMap.InfoWindowAdapter {
  private val contents: View = layoutInflater.inflate(R.layout.info_window, null)
  
  override fun getInfoWindow(marker: Marker?): View? {
    return null
  }

  override fun getInfoContents(marker: Marker): View {
    // UI elements
    val titleTextView = contents.titleTextView
    val addressTextView = contents.addressTextView
    val phoneTextView = contents.phoneTextView
    val emailTextView = contents.emailTextView
    val webTextView = contents.webTextView
    // title
    titleTextView.text = marker.title.toString()
    // get data from Tag list
    if (marker.tag is List<*>){
      val list: List<String> = marker.tag as List<String>
      addressTextView.text = list[0]
      phoneTextView.text = list[1]
      emailTextView.text = list[2]
      webTextView.text = list[3]
    }
    return contents
  }
}

Use adapter in mainActivity

Connect your CustomInfoWindowAdapter to Google Maps in your loadData function.

// Add the request to the RequestQueue
        queue.add(jsonObjectRequest)
        // Add custom info window adapter
        mMap.setInfoWindowAdapter(CustomInfoWindowAdapter())
    }

Test your project

Run and test your project. Info Window will be visible, when a marker is clicked.

Add comment

Pasi Manninen

Pasi Manninen

Pasi is a mobile and web application developer. Currently working as a senior lecturer in JAMK University of Applied Sciences. Pasi has programming experience over many decades and has taught dozens of courses since the 90's.

Recent Posts

Follow Me