diff --git a/homeassistant/components/geo_location/__init__.py b/homeassistant/components/geo_location/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..67ed9520fa4d1309f230955205e1fcb0ad6524df
--- /dev/null
+++ b/homeassistant/components/geo_location/__init__.py
@@ -0,0 +1,68 @@
+"""
+Geo Location component.
+
+This component covers platforms that deal with external events that contain
+a geo location related to the installed HA instance.
+
+For more details about this component, please refer to the documentation at
+https://home-assistant.io/components/geo_location/
+"""
+import logging
+from datetime import timedelta
+from typing import Optional
+
+from homeassistant.const import ATTR_LATITUDE, ATTR_LONGITUDE
+from homeassistant.helpers.entity import Entity
+from homeassistant.helpers.entity_component import EntityComponent
+
+_LOGGER = logging.getLogger(__name__)
+
+ATTR_DISTANCE = 'distance'
+DOMAIN = 'geo_location'
+ENTITY_ID_FORMAT = DOMAIN + '.{}'
+GROUP_NAME_ALL_EVENTS = 'All Geo Location Events'
+SCAN_INTERVAL = timedelta(seconds=60)
+
+
+async def async_setup(hass, config):
+    """Set up this component."""
+    component = EntityComponent(
+        _LOGGER, DOMAIN, hass, SCAN_INTERVAL, GROUP_NAME_ALL_EVENTS)
+    await component.async_setup(config)
+    return True
+
+
+class GeoLocationEvent(Entity):
+    """This represents an external event with an associated geo location."""
+
+    @property
+    def state(self):
+        """Return the state of the sensor."""
+        if self.distance is not None:
+            return round(self.distance, 1)
+        return None
+
+    @property
+    def distance(self) -> Optional[float]:
+        """Return distance value of this external event."""
+        return None
+
+    @property
+    def latitude(self) -> Optional[float]:
+        """Return latitude value of this external event."""
+        return None
+
+    @property
+    def longitude(self) -> Optional[float]:
+        """Return longitude value of this external event."""
+        return None
+
+    @property
+    def state_attributes(self):
+        """Return the state attributes of this external event."""
+        data = {}
+        if self.latitude is not None:
+            data[ATTR_LATITUDE] = round(self.latitude, 5)
+        if self.longitude is not None:
+            data[ATTR_LONGITUDE] = round(self.longitude, 5)
+        return data
diff --git a/homeassistant/components/geo_location/demo.py b/homeassistant/components/geo_location/demo.py
new file mode 100644
index 0000000000000000000000000000000000000000..8e8d82110860531c6b3763fa0cf0f28308b7e333
--- /dev/null
+++ b/homeassistant/components/geo_location/demo.py
@@ -0,0 +1,132 @@
+"""
+Demo platform for the geo location component.
+
+For more details about this platform, please refer to the documentation
+https://home-assistant.io/components/demo/
+"""
+import logging
+import random
+from datetime import timedelta
+from math import pi, cos, sin, radians
+
+from typing import Optional
+
+from homeassistant.components.geo_location import GeoLocationEvent
+from homeassistant.helpers.event import track_time_interval
+
+_LOGGER = logging.getLogger(__name__)
+
+AVG_KM_PER_DEGREE = 111.0
+DEFAULT_UNIT_OF_MEASUREMENT = "km"
+DEFAULT_UPDATE_INTERVAL = timedelta(minutes=1)
+MAX_RADIUS_IN_KM = 50
+NUMBER_OF_DEMO_DEVICES = 5
+
+EVENT_NAMES = ["Bushfire", "Hazard Reduction", "Grass Fire", "Burn off",
+               "Structure Fire", "Fire Alarm", "Thunderstorm", "Tornado",
+               "Cyclone", "Waterspout", "Dust Storm", "Blizzard", "Ice Storm",
+               "Earthquake", "Tsunami"]
+
+
+def setup_platform(hass, config, add_entities, discovery_info=None):
+    """Set up the Demo geo locations."""
+    DemoManager(hass, add_entities)
+
+
+class DemoManager:
+    """Device manager for demo geo location events."""
+
+    def __init__(self, hass, add_entities):
+        """Initialise the demo geo location event manager."""
+        self._hass = hass
+        self._add_entities = add_entities
+        self._managed_devices = []
+        self._update(count=NUMBER_OF_DEMO_DEVICES)
+        self._init_regular_updates()
+
+    def _generate_random_event(self):
+        """Generate a random event in vicinity of this HA instance."""
+        home_latitude = self._hass.config.latitude
+        home_longitude = self._hass.config.longitude
+
+        # Approx. 111km per degree (north-south).
+        radius_in_degrees = random.random() * MAX_RADIUS_IN_KM / \
+            AVG_KM_PER_DEGREE
+        radius_in_km = radius_in_degrees * AVG_KM_PER_DEGREE
+        angle = random.random() * 2 * pi
+        # Compute coordinates based on radius and angle. Adjust longitude value
+        # based on HA's latitude.
+        latitude = home_latitude + radius_in_degrees * sin(angle)
+        longitude = home_longitude + radius_in_degrees * cos(angle) / \
+            cos(radians(home_latitude))
+
+        event_name = random.choice(EVENT_NAMES)
+        return DemoGeoLocationEvent(event_name, radius_in_km, latitude,
+                                    longitude, DEFAULT_UNIT_OF_MEASUREMENT)
+
+    def _init_regular_updates(self):
+        """Schedule regular updates based on configured time interval."""
+        track_time_interval(self._hass, lambda now: self._update(),
+                            DEFAULT_UPDATE_INTERVAL)
+
+    def _update(self, count=1):
+        """Remove events and add new random events."""
+        # Remove devices.
+        for _ in range(1, count + 1):
+            if self._managed_devices:
+                device = random.choice(self._managed_devices)
+                if device:
+                    _LOGGER.debug("Removing %s", device)
+                    self._managed_devices.remove(device)
+                    self._hass.add_job(device.async_remove())
+        # Generate new devices from events.
+        new_devices = []
+        for _ in range(1, count + 1):
+            new_device = self._generate_random_event()
+            _LOGGER.debug("Adding %s", new_device)
+            new_devices.append(new_device)
+            self._managed_devices.append(new_device)
+        self._add_entities(new_devices)
+
+
+class DemoGeoLocationEvent(GeoLocationEvent):
+    """This represents a demo geo location event."""
+
+    def __init__(self, name, distance, latitude, longitude,
+                 unit_of_measurement):
+        """Initialize entity with data provided."""
+        self._name = name
+        self._distance = distance
+        self._latitude = latitude
+        self._longitude = longitude
+        self._unit_of_measurement = unit_of_measurement
+
+    @property
+    def name(self) -> Optional[str]:
+        """Return the name of the event."""
+        return self._name
+
+    @property
+    def should_poll(self):
+        """No polling needed for a demo geo location event."""
+        return False
+
+    @property
+    def distance(self) -> Optional[float]:
+        """Return distance value of this external event."""
+        return self._distance
+
+    @property
+    def latitude(self) -> Optional[float]:
+        """Return latitude value of this external event."""
+        return self._latitude
+
+    @property
+    def longitude(self) -> Optional[float]:
+        """Return longitude value of this external event."""
+        return self._longitude
+
+    @property
+    def unit_of_measurement(self):
+        """Return the unit of measurement."""
+        return self._unit_of_measurement
diff --git a/tests/components/geo_location/__init__.py b/tests/components/geo_location/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..56fc7d9fc92815ff00547e46ee7f1ab92205dbe2
--- /dev/null
+++ b/tests/components/geo_location/__init__.py
@@ -0,0 +1 @@
+"""The tests for Geo Location platforms."""
diff --git a/tests/components/geo_location/test_demo.py b/tests/components/geo_location/test_demo.py
new file mode 100644
index 0000000000000000000000000000000000000000..158e5d619687f3e96eb78ce7b68630b0161464b2
--- /dev/null
+++ b/tests/components/geo_location/test_demo.py
@@ -0,0 +1,63 @@
+"""The tests for the demo platform."""
+import unittest
+from unittest.mock import patch
+
+from homeassistant.components import geo_location
+from homeassistant.components.geo_location.demo import \
+    NUMBER_OF_DEMO_DEVICES, DEFAULT_UNIT_OF_MEASUREMENT, \
+    DEFAULT_UPDATE_INTERVAL
+from homeassistant.setup import setup_component
+from tests.common import get_test_home_assistant, assert_setup_component, \
+    fire_time_changed
+import homeassistant.util.dt as dt_util
+
+CONFIG = {
+    geo_location.DOMAIN: [
+        {
+            'platform': 'demo'
+        }
+    ]
+}
+
+
+class TestDemoPlatform(unittest.TestCase):
+    """Test the demo platform."""
+
+    def setUp(self):
+        """Initialize values for this testcase class."""
+        self.hass = get_test_home_assistant()
+
+    def tearDown(self):
+        """Stop everything that was started."""
+        self.hass.stop()
+
+    def test_setup_platform(self):
+        """Test setup of demo platform via configuration."""
+        utcnow = dt_util.utcnow()
+        # Patching 'utcnow' to gain more control over the timed update.
+        with patch('homeassistant.util.dt.utcnow', return_value=utcnow):
+            with assert_setup_component(1, geo_location.DOMAIN):
+                self.assertTrue(setup_component(self.hass, geo_location.DOMAIN,
+                                                CONFIG))
+
+            # In this test, only entities of the geo location domain have been
+            # generated.
+            all_states = self.hass.states.all()
+            assert len(all_states) == NUMBER_OF_DEMO_DEVICES
+
+            # Check a single device's attributes.
+            state_first_entry = all_states[0]
+            self.assertAlmostEqual(state_first_entry.attributes['latitude'],
+                                   self.hass.config.latitude, delta=1.0)
+            self.assertAlmostEqual(state_first_entry.attributes['longitude'],
+                                   self.hass.config.longitude, delta=1.0)
+            assert state_first_entry.attributes['unit_of_measurement'] == \
+                DEFAULT_UNIT_OF_MEASUREMENT
+            # Update (replaces 1 device).
+            fire_time_changed(self.hass, utcnow + DEFAULT_UPDATE_INTERVAL)
+            self.hass.block_till_done()
+            # Get all states again, ensure that the number of states is still
+            # the same, but the lists are different.
+            all_states_updated = self.hass.states.all()
+            assert len(all_states_updated) == NUMBER_OF_DEMO_DEVICES
+            self.assertNotEqual(all_states, all_states_updated)
diff --git a/tests/components/geo_location/test_init.py b/tests/components/geo_location/test_init.py
new file mode 100644
index 0000000000000000000000000000000000000000..54efe977bf91794ced27c2b39308ff76900f1a79
--- /dev/null
+++ b/tests/components/geo_location/test_init.py
@@ -0,0 +1,20 @@
+"""The tests for the geo location component."""
+from homeassistant.components import geo_location
+from homeassistant.components.geo_location import GeoLocationEvent
+from homeassistant.setup import async_setup_component
+
+
+async def test_setup_component(hass):
+    """Simple test setup of component."""
+    result = await async_setup_component(hass, geo_location.DOMAIN)
+    assert result
+
+
+async def test_event(hass):
+    """Simple test of the geo location event class."""
+    entity = GeoLocationEvent()
+
+    assert entity.state is None
+    assert entity.distance is None
+    assert entity.latitude is None
+    assert entity.longitude is None