Building Interactive Maps

Christopher Reichert rets, idx, mls, maps, javascript

Map real estate search apps are a great way to enhance web app functionality and user-engagement. They can be significantly more user-friendly to use for real estate listing search utilities than standard html forms. Many features in the SimplyRETS API are built with this specific use-case in mind.

SimplyRETS real estate map search

For the uninitiated, SimplyRETS leverages your RETS or RESO Web API data feed to provide a more modern and robust API for developing real estate software using listing, open house, and agent data from your MLS. Our services are designed to be simple and effective for any developer to use productively.

In this tutorial, I’ll outline how to build a simple map-based search app using the SimplyRETS API. If you want to skip to the code or follow along with a complete sample, you can use our Github Examples Repo. We’ve prepared a live demo with the code from this tutorial here.

Sections

Background details

The primary feature in the SimplyRETS API which helps with map-based searching is the points query parameter. The points parameter is simply a list of latitude/longitude coordinates (separated by a comma). See our interactive documentation for more details and other query parameters.

A raw query url to our listings api endpoint which uses points should look like:

https://api.simplyrets.com/properties?points=29.723837146389066,-95.69778442382812&points=29.938275329718987,-95.69778442382812&points=29.938275329718987,-95.32974243164061&points=29.723837146389066,-95.32974243164061

Note that points is specified once for each point in a polygon made up of latitude/longitude coordinates.

In order to utilize the points parameter from a web app, we need a few external utilities and libraries for rendering maps on html/javascript pages. I’ll be using Leaflet and OpenStreetMap as a starting point to build the map and access basic map tools. We will also be using a Leaflet extension called Leaflet.Draw

I will use jQuery to make some of the Javascript more simple. However, jQuery is not strictly necessary for this app.

All the tools used in this tutorial are free and open source. You are free to download and use them in your own projects. I have also included the source code for this tutorial on our Github. if you’d like to follow along or reuse it.

Once the foundation is laid, I’ll make API calls to the SimplyRETS listings endpoint, https://api.simplyrets.com/properties, using javascript to retrieve the listing data and populate the map.

The end result of this tutorial should be a simple mapping application which can be adapted for use in many different situations. You can check out a live demo here.

Getting set up

To get started, create a file named index.html and include an html page shell.

<html>
    <head>
        <title>SimplyRETS interactive map search</title>
        <script>
            $(document).ready(function () {
                console.log("SimplyRETS Map Search")
            })
        </script>
    </head>
    <body>
        <div id="#map" style="height: 100%"></div>
    </body>
</html>

This html is enough to get started. You can open this file directly in a web browser (without a web server). Type file:/// in your address bar to navigate to the right folder. Don’t worry if the page is all white yet.

Include dependencies

Next, we need to include some scripts and stylesheets in order to use Leaflet, OpenStreetMap, and jQuery. For the purposes of this tutorial, I will use CDN links. It’s not necessary to download any code manually.

<head>
    <link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet/v0.7.7/leaflet.css" />
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/0.2.3/leaflet.draw.css" />
    <!--[if lte IE 8]>
      <link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet/v0.7.7/leaflet.ie.css" />
    <![endif]-->
</head>

Include these stylesheets inside the <head> tag of index.html. Do the same for the javascrtipt:

<head>
    <!-- ...stylesheets... -->

    <script src="https://code.jquery.com/jquery-1.10.2.js"></script>
    <script src="http://cdn.leafletjs.com/leaflet/v0.7.7/leaflet.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/0.2.3/leaflet.draw.js"></script>
</head>

JavaScript

Now that we have our project setup and a code skeleton to work with, we can start writing some javascript to implement our map logic.

$(document).ready(function () {
    // Code goes here.
})

This line of jQuery code ensures that the web page is ready to be manipulated. jQuery handles the details for us. Anything inside the ready(function() { code block will be executed once when the DOM can be manipulated.

Next, we need to setup a Leaflet map.

var map = new L.Map("map")

L.tileLayer("http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
    attribution:
        '&copy; <a href="http://openstreetmap.org">OpenStreetMap</a> contributors',
    maxZoom: 18,
}).addTo(map)

The capital L is an identifier for the Leaflet namespace. Functions called after the L. are specific to the Leaflet API. In this case, we are initializing a map, then creating a tileLayer which we add to the map. In short, Leaflet applications work by manipulating various layers within the context of a map.

The tile layer on our map is backed by an OpenStreetMap tile png. The maxZoom is set to 18. The higher the maxZoom, the further away the the map will be allowed to zoom out. The final addTo call is where the tile layer is directly associated with the map object.

We use the latitude/longitude coordinates of Houston, Texas to center the map since the SimplyRETS test data is located in Houston.

var houston = new L.LatLng(29.97, -95.35)
map.setView(houston, 13)

L.LatLng is another convenience class which the Leaflet API provides. A LatLng object can be applied directly to the map as a view using setView. The second argument to setView is the initial zoom. 13 is a good enough value to see the entire city.

Drawing on the map

In order to draw Polgons on the map, we need to set up a Leaflet FeatureGroup. FeatureGroup’s are another Leaflet class capable of grouping and interacting with other layers on the map. Using FeatureGroups, we can add our polygon(s) to the map using only a few lines of code.

We set up a basic feature group name drawnItems (for drawn items) and add it to the map using addLayer.

var drawnItems = new L.FeatureGroup()
map.addLayer(drawnItems)

Now, we need to attach the same drawnItems FeatureGroup to a Draw control.

var drawControl = new L.Control.Draw({
    edit: {
        featureGroup: drawnItems,
    },
    draw: {
        circle: false,
        marker: false,
        polyline: false,
    },
})

map.addControl(drawControl)

We created a new Draw control, using our FeatureGroup for drawn items. To connect the Draw control with the map, we use the map.addControl function call (similar to map.addLayer).

If you load the map now, you will see a control panel in the upper left corner:

SimplyRETS map search controls

For the purpose of this tutorial, we focus on the square and polgon tools. Using the draw options when creating the Draw control, we can disable control components which we are not using (e.g. circle, marker, and polyline). For more information see the Leaflet.Draw documentation

Calling the listings API

Now that we have our map, layers, and draw control setup we can finally start pulling listings from SimplyRETS. In order for the draw control to do this, we must implement an event listener callback function which describes what to do when shapes are created on the map using the draw control

Leaflet uses event methods to hook into the Map and run functions. The on function is used to add an event listener to an object (e.g. a Map). For example, map.on('click', function(e) { alert(e.latlng); }); could be used to run a function when a user clicks on the map.

We need to hook into our draw controls 'draw:created' event in order to properly handle draw events on our map.

Since our event listener will be run when a polygon is overlayed on the map, we can outline a set of steps to request data from the SimplyRETS api using the lat/lng coordinates drawn by the user.

  1. Get latitude longitude coordinates from the Draw Control.
  2. Request listings from the SimplyRETS API using the latitude/longitude points as query parameters
  3. Place each listing in the API response as a point on the map.

Start by creating an event callback for 'draw:created'.

map.on("draw:created", function (e) {
    var latLngs = $.map(e.layer.getLatLngs(), function (o) {
        return { name: "points", value: o.lat + "," + o.lng }
    })
})

First, the callback extracts and formats the latitude/longitude points from the new polygon. The argument passed to function(e) { .. } is the event. Unsurprisingly, the event contains a layer for the new polygon. Calling getLatLngs() on the layer will return a list of latitude/longitude points.

The $.map function from jQuery is used to format the lat/lng points into a suitable format for the SimplyRETS points query parameter. The function passed to $.map returns a name/value pair for each point in the polygon and is stored in the latLng var.

An ajax request is then created to submit the latLng points to the SimplyRETS properties endpoint and retrieve listings. We pass in the latLngs variable as the ajax data parameter. This means the list of latitude/longitude points are translated into query parameters in the GET request.

In the beforeSend function, we setup authentication to the SimplyRETS api using test credentials. Using test credentials here is for test purposes only. Use caution when determining how to handle authenticating your credentials from your app.

    $.ajax({
        url: 'https://api.simplyrets.com/properties',
        data: latLngs,
        beforeSend: function(xhr) {
            // Create the correct authentication headers for the
            // SimplyRETS API. Using our trial credentials here is for
            // test purposes _only_. Use caution when determining how to
            // handle your credentials w/ your specific use-case.
            xhr.setRequestHeader("Authorization", "Basic " + btoa("simplyrets:simplyrets"))
        },
        success: function(data) {
            $.each (data, function (idx) {
                var markerLocation = new L.LatLng(data[idx].geo.lat, data[idx].geo.lng);
                var marker = new L.Marker(markerLocation);
                map.addLayer(marker);
            });
        }
    });

    map.addLayer(e.layer);
});

The final bit that’s needed, is a function to run on a successful request with the retrieved listing data. The $.ajax function provides an option named success for a function to with the response data.

Since the data retrieved is in JSON format by default, we can loop directly over each listing in the results using jQuery $.each function.

For each listing, we create a marker location and corresponding marker. To tie it all together, we use map.addLayer to add the marker to the map.

Conclusion

Voila, you should now see markers within a polygon region on your map. You can try a live demo of the code here. The end result of our code can now be used as a starting point in any application which requires map-based search. The code is quite small and can be easily customized:

<html>
    <head>
        <title>SimplyRETS Map Search</title>

        <link
            rel="stylesheet"
            href="http://cdn.leafletjs.com/leaflet/v0.7.7/leaflet.css"
        />
        <link
            rel="stylesheet"
            href="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/0.2.3/leaflet.draw.css"
        />
        <!--[if lte IE 8
            ]><link
                rel="stylesheet"
                href="http://cdn.leafletjs.com/leaflet/v0.7.7/leaflet.ie.css"
        /><![endif]-->

        <script src="https://code.jquery.com/jquery-1.10.2.js"></script>
        <script src="http://cdn.leafletjs.com/leaflet/v0.7.7/leaflet.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/0.2.3/leaflet.draw.js"></script>

        <script language="javascript">
            $(document).ready(function () {
                var map = new L.Map("map")

                L.tileLayer(
                    "http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
                    {
                        attribution:
                            '&copy; %lt;a href="http://openstreetmap.org">OpenStreetMap</a> contributors',
                    }
                ).addTo(map)

                var houston = new L.LatLng(29.97, -95.35)
                map.setView(houston, 10)

                var drawnItems = new L.FeatureGroup()
                map.addLayer(drawnItems)

                var drawControl = new L.Control.Draw({
                    edit: {
                        featureGroup: drawnItems,
                    },
                })

                map.addControl(drawControl)
                map.on("draw:created", function (e) {
                    var latLngs = $.map(e.layer.getLatLngs(), function (o) {
                        return { name: "points", value: o.lat + "," + o.lng }
                    })

                    $.ajax({
                        url: "https://api.simplyrets.com/properties",
                        data: latLngs,
                        beforeSend: function (xhr) {
                            xhr.setRequestHeader(
                                "Authorization",
                                "Basic " + btoa("simplyrets:simplyrets")
                            )
                        },
                        success: function (data) {
                            $.each(data, function (idx) {
                                var markerLocation = new L.LatLng(
                                    data[idx].geo.lat,
                                    data[idx].geo.lng
                                )
                                var marker = new L.Marker(markerLocation)
                                map.addLayer(marker)
                            })
                        },
                    })

                    map.addLayer(e.layer)
                })
            })
        </script>
    </head>
    <body>
        <div id="map" style="height: 100%"></div>
    </body>
</html>

The example code can be found on the SimplyRETS examples repo on Github and is completely free to take and use as you wish. Send us a message any time if you have questions or you’re interested in learning more about.

— Christopher @ SimplyRETS

Comments

SimplyRETS logoSimplyRETS

The easiest way to build real estate software with your MLS data.

Learn more at simplyrets.com, get started with the API docs.