Real estate

Maps and location intelligence for real estate and rental search websites

Property and real estate search websites and portals like Zillow, Trulia or Rightmove have disrupted the real estate market in the past decade. They made it much easier for both sides on the market to find each other.

Initially, the key to success was to have broadest and comprehensive property listings. As the number of global and regional websites was increasing, focus moved to offer best-in-class features to win the competition.

Having broad and detailed property listings is not enough anymore. Visitors expect smooth and highly visual experience, rich contextual data and powerful search, filtering, and ranking functionality.

To stand out, property search websites need to provide the most contextually relevant results, location insights, and additional services. And more often than not the area, connectivity, and surrounding amenities are more important than property features alone.

Geoapify offers a unique combination of feature-rich APIs that can help to address these requirements and greatly increase visitor’s engagement

Maps

Our highly dynamic, customizable maps can be styled to perfectly match your website design and branding. Thanks to compatibility with most map visualization libraries and permissive usage policy, you can easily use our maps with any technology, data set, or usage scenario.

Location search

Our Geolocation and Geocoding APIs can help to identify search areas based on visitor location, as well as parse, validate and locate address data he or she enters into the search form.

Reachability analysis

When most property search websites only offer filtering by bounding box or radius, Geoapify Reachability API allows filtering by much more precise, realistic travel time areas. For most urban areas our Reachability API helps to eliminate unreachable place and discover places that would normally be filtered out by the more primitive distance based search.

Public transport reachability in Munich, Germany
Public transport reachability in Munich, Germany

Filtering by boundaries

With our Geometry API, you can offer advanced filtering capabilities that include a combination of reachability areas, user-defined and administrative boundaries.

Statistics and data

Geoapify Statistics and Data APIs can enrich the results with the census, economics, age, and other population-related data. Our Places and Boundaries APIs give a comprehensive listing of amenities in the given area or near the property.

Multiple data sources

Our platform can operate with spatial data from different sources to generate very personalized listings – for instance, only show the properties located in an area with a higher income population and schools reachable within 30 minutes of walking.

Commute and areas close to Park&Ride in Berlin, Germany
Commute and areas close to Park&Ride in Berlin, Germany

Solutions and use-cases

We’ve prepared some examples of solutions that can bring your real-estate website to success

Location intelligence

Location intelligence

Location intelligence for smart place search Use geospatial methods and algorithms to find the optimal place Looking for a place to live, a new office or preparing for vacations? Then a good location is one of the most important components...
Travel time maps

Travel time maps

Travel time maps Travel time maps or driving time maps or isochrones show how far can you go from a location within a certain time Travel time is one of the most important criteria when you choose a location. That’s why a travel time m...

Geoapify provides a platform, products, and services to help you benefit location intelligence

  • Check our Maps API that allows developing a map from scratch.
  • Try our Mapifator map builder that can be used to create a custom map with a graphical interface.
  • Or contact us and we will advise you the best fitting to your use case technologies and provide a turn-key solution for you!

Nominatim vs Photon geocoder

Looking up a location given free-form address is one of the most frequent operations on geospatial data. Almost every business needs to recognize and locate customer addresses. At the same time, differences in languages, country-specific address and postcode formats, typos and ambiguous names make it very hard to do it right.

This is why one normally uses a geocoder – a specialized search engine that is designed to return the most relevant location for a given address string.

Feature comparison

Nominatim geocoder is a “de-facto” standard in the world of open source geocoding engines. It is developed and maintained by the OpenStreetMap community.

Photon is another popular open-source geocoder with the focus on search-as-you-type suggestions and typos correction. It is developed and maintained by Komoot.

Both geocoders are fine pieces of software, supporting both forward and reverse geocoding and primarily use the OpenStreetMap dataset for address lookups.

FeatureNominatimPhoton
Structured searchyesno
Search-as-you-typenoyes
Hardware requirementshighmedium
Typo toleranceaveragegood
Full-text searchnoyes
Filtering resultsbounding box
county code
bounding box
OSM tag name and value
Special phrases (in, near)yesno
Configurable results rangingnolocation bias
DatabasePostGISElasticsearch
Data sourcesOpenStreetMap
Wikipedia
US Tiger & Postcodes
UK Postcodes
OpenStreetMap
Response formatsGeoJSON
JSON
HTML
XML
GeoJSON
Returned datafull, including geometrybasic
User interfaceyesno

Conclusion

So, which geocoder is the best, Nominatim or Photon? As usual, the answer is – it depends.

Photon excels in search-as-you-type scenarios. It tolerates typos and spelling mistakes. Photon is good for a general, free-form text geocoding. It is relatively lightweight and not very difficult to maintain – setting up your own instance takes from few hours to few days.

Nominatim on another hand has superior structured address search and filtering capabilities. It supports a number of output formats and advanced filters. Nominatim works best in scenarios when you at least roughly know what you’re looking for and where. Nominatim comes with significant complexity, hardware requirements, and maintenance overhead. It may take days and weeks to configure your own functional Nominatim instance.

In both cases, you’ll need to have expertise and time to set up your own geocoder instance. Alternatively, you can use our managed Photon and Nominatim geocoder instances. We take care of all aspects – configuration, updates, maintenance, availability, and security. We also provide consulting and custom software development. In case you’re looking for high-volume batch geocoding or cannot use 3rd party services due to security or privacy regulations, we can also set up a dedicated geocoder instance for you.

Location autocomplete with Angular

Geoapify provides an API which allows searching a location by the query string. In this article, we provide you an example of how to create a location autocomplete field by using Geoapify Geocoding API.

In our example, we use Angular platform together with Angular Material framework.

Geoapify Geocoding API Playground contains a working example of the location autocomplete described in this article.

Pre-requirements

  • Angular Material installed. Read about installation here.

Step 1. Create a new component for a location autocomplete

Create a new component with Angular Cli command:

ng generate component Autocomplete

Step 2. Required imports

To make the component from this tutorial work, it’s required to import the following modules into your module:

@NgModule({
  declarations: [..., AutocompleteComponent],
  imports: [
    CommonModule,
    HttpClientModule,
    MatInputModule,
    MatFormFieldModule,
    FormsModule,
    ReactiveFormsModule,
    MatAutocompleteModule,
    MatTooltipModule,
    ...
  ],
  exports: [...]
})

Step 3. HTML template

We use the following components:

  • mat-form-input
  • mat-input
  • mat-autocomplete
  • mat-tooltip
  • formControl from ReactiveFormsModule.

Add the following code into your component html template:

<mat-form-field floatLabel="never">
  <input matInput type="text" [matAutocomplete]="auto" 
    [formControl]="inputFieldFormControl" placeholder="Enter location here" />

  <mat-autocomplete #auto="matAutocomplete" autoActiveFirstOption>
    <mat-option *ngFor="let option of searchOptions | async" [value]="option.shortAddress" 
      (onSelectionChange)="optionSelectionChange(option, $event)"
      [matTooltip]="option.fullAddress" matTooltipShowDelay="1000">
      <span class="mat-body">{{ option.shortAddress }}</span>
    </mat-option>
  </mat-autocomplete>
</mat-form-field>

Here is an idea of how the location autocomplete will work:

  • The string value will be stored in the “inputFieldFormControl” reactive form field.
  • The “inputFieldFormControl” field fires an event when its value was changed.
  • When an event fired we send an HTTP Get request to Geocoding API to retrieve place suggestions and store them in “searchOptions”.
  • mat-autocomplete loads options from the “searchOptions” asynchronously.

Step 4. AutocompleteComponent class

Here is the code of the AutocompleteComponent class:

import { Component, Output, EventEmitter, OnDestroy } from '@angular/core';
import { MatOptionSelectionChange } from '@angular/material';
import { Subject, Subscription } from 'rxjs';
import { FormControl } from '@angular/forms';
import { HttpClient } from '@angular/common/http';

@Component({
  selector: 'app-autocomplete',
  templateUrl: './autocomplete.component.html',
  styleUrls: ['./autocomplete.component.scss']
})
export class AutocompleteComponent implements OnDestroy {
  @Output()
  locationChange: EventEmitter<PlaceSuggestion> = new EventEmitter<PlaceSuggestion>();

  searchOptions: Subject<PlaceSuggestion[]> = new Subject<PlaceSuggestion[]>();
  inputFieldFormControl: FormControl = new FormControl();

  private valueChangesSub: Subscription;
  private choosenOption: PlaceSuggestion;

  private userInputTimeout: number;
  private requestSub: Subscription;

  constructor(private http: HttpClient) {
    this.valueChangesSub = this.inputFieldFormControl.valueChanges.subscribe((value) => {
      if (this.userInputTimeout) {
        window.clearTimeout(this.userInputTimeout);
      }

      if (this.choosenOption && this.choosenOption.shortAddress === value) {
        this.searchOptions.next(null);
        return;
      }

      if (!value || value.length < 3) {
        // do not need suggestions until for less than 3 letters
        this.searchOptions.next(null);
        return;
      }

      this.userInputTimeout = window.setTimeout(() => {
        this.generateSuggestions(value);
      }, 300);
    });
  }

  ngOnDestroy() {
    this.valueChangesSub.unsubscribe();
  }

  private generateSuggestions(text: string) {
    const url = `https://api.geoapify.com/v1/geocode/autocomplete?text=${text}&limit=5&apiKey=${YOUR_API_KEY}`;

    if (this.requestSub) {
      this.requestSub.unsubscribe();
    }

    this.requestSub = this.http.get(url).subscribe((data: GeoJSON.FeatureCollection) => {
      const placeSuggestions = data.features.map(feature => {
        const properties: GeocodingFeatureProperties = (feature.properties as GeocodingFeatureProperties);

        return {
          shortAddress: this.generateShortAddress(properties),
          fullAddress: this.generateFullAddress(properties),
          data: properties
        }
      });

      this.searchOptions.next(placeSuggestions.length ? placeSuggestions : null);
    }, err => {
      console.log(err);
    });
  }

  private generateShortAddress(properties: GeocodingFeatureProperties): string {
    let shortAddress = properties.name;

    if (!shortAddress && properties.street && properties.housenumber) {
      // name is not set for buildings
      shortAddress = `${properties.street} ${properties.housenumber}`;
    }

    shortAddress += (properties.postcode && properties.city) ? `, ${properties.postcode}-${properties.city}`: '';
    shortAddress += (!properties.postcode && properties.city && properties.city  !== properties.name) ? `, ${properties.city}`: '';
    shortAddress += (properties.country && properties.country !== properties.name) ? `, ${properties.country}` : '';

    return shortAddress;
  }

  private generateFullAddress(properties: GeocodingFeatureProperties): string {
    let fullAddress = properties.name;
    fullAddress += properties.street ? `, ${properties.street}` : '';
    fullAddress += properties.housenumber ? ` ${properties.housenumber}` : '';
    fullAddress += (properties.postcode && properties.city) ? `, ${properties.postcode}-${properties.city}`: '';
    fullAddress += (!properties.postcode && properties.city && properties.city  !== properties.name) ? `, ${properties.city}`: '';
    fullAddress += properties.state ? `, ${properties.state}`: '';
    fullAddress += (properties.country && properties.country !== properties.name) ? `, ${properties.country}` : '';
    return fullAddress;
  }

  public optionSelectionChange(option: PlaceSuggestion, event: MatOptionSelectionChange) {
    if (event.isUserInput) {
      this.choosenOption = option;
      this.locationChange.emit(option);
    }
  }
}

export interface PlaceSuggestion {
  shortAddress: string;
  fullAddress: string;
  data: GeocodingFeatureProperties;
}

interface GeocodingFeatureProperties {
  name: string;
  country: string;
  state: string;
  postcode: string;
  city: string;
  street: string;
  housenumber: string;
}

inputFieldFormControl

As described above, the field holds the value of the search string, which is observed by this.inputFieldFormControl.valueChanges.subscribe().

To keep the code clean we save the created subscription in the variable valueChangesSub and unsubscribe on destroy.

userInputTimeout

To avoid too many unnecessary requests and decrease the application load, we perform HTTP request only when a user stops to type.

This implemented by using userInputTimeout, which sets every time when the user enters a new value.

searchOptions

Contain the values returned by Geocoding API and displayed by autocomplete control.

When we set this.searchOptions.next(null), the autocomplete control is hidden.

PlaceSuggestion & GeocodingFeatureProperties

We use the interfaces to simplify work with JSON object returned by the Geocoding API. We export PlaceSuggestion to be able to use the interface in other components, services, and modules.

locationChange

Is an Output() of the Autocomplete component. We this.locationChange.emit(option) when a new place suggestion was selected.

generateShortAddress() & generateFullAddress()

As Geocoding API returns value with address components, but not a formatted address, we need to generate an address string of required format. generateShortAddress() & generateFullAddress() are examples of how an address string could be generated.

Step 5. Add the Location Autocomplete component into your code

The new component could be added into your code in the following way:

<app-autocomplete (locationChange)="autocompleteChanged($event)"></app-autocomplete>

When a new value was chosen in the location autocomplete, the event is fired:

autocompleteChanged(value: PlaceSuggestion) {}