diff --git a/homeassistant/components/sensor/geo_rss_events.py b/homeassistant/components/sensor/geo_rss_events.py
new file mode 100644
index 0000000000000000000000000000000000000000..484dd67e0e451297e0c40aa41361f10fd0afa472
--- /dev/null
+++ b/homeassistant/components/sensor/geo_rss_events.py
@@ -0,0 +1,243 @@
+"""
+Generic GeoRSS events service.
+
+Retrieves current events (typically incidents or alerts) in GeoRSS format, and
+shows information on events filtered by distance to the HA instance's location
+and grouped by category.
+
+For more details about this platform, please refer to the documentation at
+https://home-assistant.io/components/sensor.geo_rss_events/
+"""
+
+import logging
+from collections import namedtuple
+from datetime import timedelta
+
+import voluptuous as vol
+
+import homeassistant.helpers.config_validation as cv
+from homeassistant.components.sensor import PLATFORM_SCHEMA
+from homeassistant.const import (STATE_UNKNOWN, CONF_UNIT_OF_MEASUREMENT,
+                                 CONF_NAME)
+from homeassistant.helpers.entity import Entity
+from homeassistant.util import Throttle
+
+REQUIREMENTS = ['feedparser==5.2.1', 'haversine==0.4.5']
+
+_LOGGER = logging.getLogger(__name__)
+
+ATTR_CATEGORY = 'category'
+ATTR_DISTANCE = 'distance'
+ATTR_TITLE = 'title'
+
+CONF_CATEGORIES = 'categories'
+CONF_RADIUS = 'radius'
+CONF_URL = 'url'
+
+DEFAULT_ICON = 'mdi:alert'
+DEFAULT_NAME = "Event Service"
+DEFAULT_RADIUS_IN_KM = 20.0
+DEFAULT_UNIT_OF_MEASUREMENT = 'Events'
+
+DOMAIN = 'geo_rss_events'
+
+# Minimum time between updates from the source.
+MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=1)
+
+SCAN_INTERVAL = timedelta(minutes=5)
+
+PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
+    vol.Required(CONF_URL): cv.string,
+    vol.Optional(CONF_RADIUS, default=DEFAULT_RADIUS_IN_KM): vol.Coerce(float),
+    vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
+    vol.Optional(CONF_CATEGORIES, default=[]): vol.All(cv.ensure_list,
+                                                       [cv.string]),
+    vol.Optional(CONF_UNIT_OF_MEASUREMENT,
+                 default=DEFAULT_UNIT_OF_MEASUREMENT): cv.string,
+})
+
+
+def setup_platform(hass, config, add_devices, discovery_info=None):
+    """Set up the GeoRSS component."""
+    # Grab location from config
+    home_latitude = hass.config.latitude
+    home_longitude = hass.config.longitude
+    url = config.get(CONF_URL)
+    radius_in_km = config.get(CONF_RADIUS)
+    name = config.get(CONF_NAME)
+    categories = config.get(CONF_CATEGORIES)
+    unit_of_measurement = config.get(CONF_UNIT_OF_MEASUREMENT)
+
+    _LOGGER.debug("latitude=%s, longitude=%s, url=%s, radius=%s",
+                  home_latitude, home_longitude, url, radius_in_km)
+
+    # Initialise update service.
+    data = GeoRssServiceData(home_latitude, home_longitude, url, radius_in_km)
+    data.update()
+
+    # Create all sensors based on categories.
+    devices = []
+    if not categories:
+        device = GeoRssServiceSensor(None, data, name, unit_of_measurement)
+        devices.append(device)
+    else:
+        for category in categories:
+            device = GeoRssServiceSensor(category, data, name,
+                                         unit_of_measurement)
+            devices.append(device)
+    add_devices(devices, True)
+
+
+class GeoRssServiceSensor(Entity):
+    """Representation of a Sensor."""
+
+    def __init__(self, category, data, service_name, unit_of_measurement):
+        """Initialize the sensor."""
+        self._category = category
+        self._data = data
+        self._service_name = service_name
+        self._state = STATE_UNKNOWN
+        self._state_attributes = None
+        self._unit_of_measurement = unit_of_measurement
+
+    @property
+    def name(self):
+        """Return the name of the sensor."""
+        return '{} {}'.format(self._service_name,
+                              'Any' if self._category is None
+                              else self._category)
+
+    @property
+    def state(self):
+        """Return the state of the sensor."""
+        return self._state
+
+    @property
+    def unit_of_measurement(self):
+        """Return the unit of measurement."""
+        return self._unit_of_measurement
+
+    @property
+    def icon(self):
+        """Return the default icon to use in the frontend."""
+        return DEFAULT_ICON
+
+    @property
+    def device_state_attributes(self):
+        """Return the state attributes."""
+        return self._state_attributes
+
+    def update(self):
+        """Update this sensor from the GeoRSS service."""
+        _LOGGER.debug("About to update sensor %s", self.entity_id)
+        self._data.update()
+        # If no events were found due to an error then just set state to zero.
+        if self._data.events is None:
+            self._state = 0
+        else:
+            if self._category is None:
+                # Add all events regardless of category.
+                my_events = self._data.events
+            else:
+                # Only keep events that belong to sensor's category.
+                my_events = [event for event in self._data.events if
+                             event[ATTR_CATEGORY] == self._category]
+            _LOGGER.debug("Adding events to sensor %s: %s", self.entity_id,
+                          my_events)
+            self._state = len(my_events)
+            # And now compute the attributes from the filtered events.
+            matrix = {}
+            for event in my_events:
+                matrix[event[ATTR_TITLE]] = '{:.0f}km'.format(
+                    event[ATTR_DISTANCE])
+            self._state_attributes = matrix
+
+
+class GeoRssServiceData(object):
+    """Provides access to GeoRSS feed and stores the latest data."""
+
+    def __init__(self, home_latitude, home_longitude, url, radius_in_km):
+        """Initialize the update service."""
+        self._home_coordinates = [home_latitude, home_longitude]
+        self._url = url
+        self._radius_in_km = radius_in_km
+        self.events = None
+
+    @Throttle(MIN_TIME_BETWEEN_UPDATES)
+    def update(self):
+        """Retrieve data from GeoRSS feed and store events."""
+        import feedparser
+        feed_data = feedparser.parse(self._url)
+        if not feed_data:
+            _LOGGER.error("Error fetching feed data from %s", self._url)
+        else:
+            events = self.filter_entries(feed_data)
+            self.events = events
+
+    def filter_entries(self, feed_data):
+        """Filter entries by distance from home coordinates."""
+        events = []
+        _LOGGER.debug("%s entri(es) available in feed %s",
+                      len(feed_data.entries), self._url)
+        for entry in feed_data.entries:
+            geometry = None
+            if hasattr(entry, 'where'):
+                geometry = entry.where
+            elif hasattr(entry, 'geo_lat') and hasattr(entry, 'geo_long'):
+                coordinates = (float(entry.geo_long), float(entry.geo_lat))
+                point = namedtuple('Point', ['type', 'coordinates'])
+                geometry = point('Point', coordinates)
+            if geometry:
+                distance = self.calculate_distance_to_geometry(geometry)
+                if distance <= self._radius_in_km:
+                    event = {
+                        ATTR_CATEGORY: None if not hasattr(
+                            entry, 'category') else entry.category,
+                        ATTR_TITLE: None if not hasattr(
+                            entry, 'title') else entry.title,
+                        ATTR_DISTANCE: distance
+                    }
+                    events.append(event)
+        _LOGGER.debug("%s events found nearby", len(events))
+        return events
+
+    def calculate_distance_to_geometry(self, geometry):
+        """Calculate the distance between HA and provided geometry."""
+        distance = float("inf")
+        if geometry.type == 'Point':
+            distance = self.calculate_distance_to_point(geometry)
+        elif geometry.type == 'Polygon':
+            distance = self.calculate_distance_to_polygon(
+                geometry.coordinates[0])
+        else:
+            _LOGGER.warning("Not yet implemented: %s", geometry.type)
+        return distance
+
+    def calculate_distance_to_point(self, point):
+        """Calculate the distance between HA and the provided point."""
+        # Swap coordinates to match: (lat, lon).
+        coordinates = (point.coordinates[1], point.coordinates[0])
+        return self.calculate_distance_to_coords(coordinates)
+
+    def calculate_distance_to_coords(self, coordinates):
+        """Calculate the distance between HA and the provided coordinates."""
+        # Expecting coordinates in format: (lat, lon).
+        from haversine import haversine
+        distance = haversine(coordinates, self._home_coordinates)
+        _LOGGER.debug("Distance from %s to %s: %s km", self._home_coordinates,
+                      coordinates, distance)
+        return distance
+
+    def calculate_distance_to_polygon(self, polygon):
+        """Calculate the distance between HA and the provided polygon."""
+        distance = float("inf")
+        # Calculate distance from polygon by calculating the distance
+        # to each point of the polygon but not to each edge of the
+        # polygon; should be good enough
+        for polygon_point in polygon:
+            coordinates = (polygon_point[1], polygon_point[0])
+            distance = min(distance,
+                           self.calculate_distance_to_coords(coordinates))
+        _LOGGER.debug("Distance from %s to %s: %s km", self._home_coordinates,
+                      polygon, distance)
+        return distance
diff --git a/requirements_all.txt b/requirements_all.txt
index 6d0635eb340786c206ed9c0cb132caba1e141ccf..a74f568c76c7eacb2c432c2dd985356b69cff0db 100644
--- a/requirements_all.txt
+++ b/requirements_all.txt
@@ -228,6 +228,7 @@ fastdotcom==0.0.1
 fedexdeliverymanager==1.0.4
 
 # homeassistant.components.feedreader
+# homeassistant.components.sensor.geo_rss_events
 feedparser==5.2.1
 
 # homeassistant.components.sensor.fitbit
@@ -286,6 +287,9 @@ ha-ffmpeg==1.7
 # homeassistant.components.media_player.philips_js
 ha-philipsjs==0.0.1
 
+# homeassistant.components.sensor.geo_rss_events
+haversine==0.4.5
+
 # homeassistant.components.mqtt.server
 hbmqtt==0.8
 
diff --git a/requirements_test_all.txt b/requirements_test_all.txt
index d5d6bbedca1a89a183e96dccd969db3d9deec90b..79e872ffa4cefb53a426ca1965611639f6d81168 100644
--- a/requirements_test_all.txt
+++ b/requirements_test_all.txt
@@ -45,6 +45,10 @@ ephem==3.7.6.0
 # homeassistant.components.climate.honeywell
 evohomeclient==0.2.5
 
+# homeassistant.components.feedreader
+# homeassistant.components.sensor.geo_rss_events
+feedparser==5.2.1
+
 # homeassistant.components.conversation
 fuzzywuzzy==0.15.1
 
@@ -54,6 +58,9 @@ gTTS-token==1.1.1
 # homeassistant.components.ffmpeg
 ha-ffmpeg==1.7
 
+# homeassistant.components.sensor.geo_rss_events
+haversine==0.4.5
+
 # homeassistant.components.mqtt.server
 hbmqtt==0.8
 
diff --git a/script/gen_requirements_all.py b/script/gen_requirements_all.py
index 99bcf80288bffbf58fcf4d3c40f698f25a91acb6..dd1602fba6f1b040c495756a533b504c63855b17 100755
--- a/script/gen_requirements_all.py
+++ b/script/gen_requirements_all.py
@@ -39,10 +39,12 @@ TEST_REQUIREMENTS = (
     'dsmr_parser',
     'ephem',
     'evohomeclient',
+    'feedparser',
     'forecastio',
     'fuzzywuzzy',
     'gTTS-token',
     'ha-ffmpeg',
+    'haversine',
     'hbmqtt',
     'holidays',
     'influxdb',
diff --git a/tests/components/sensor/test_geo_rss_events.py b/tests/components/sensor/test_geo_rss_events.py
new file mode 100644
index 0000000000000000000000000000000000000000..557def8225b31763b1dc820ba0579bfcff310b36
--- /dev/null
+++ b/tests/components/sensor/test_geo_rss_events.py
@@ -0,0 +1,143 @@
+"""The test for the geo rss events sensor platform."""
+import unittest
+from unittest import mock
+
+from homeassistant.setup import setup_component
+from tests.common import load_fixture, get_test_home_assistant
+import homeassistant.components.sensor.geo_rss_events as geo_rss_events
+
+URL = 'http://geo.rss.local/geo_rss_events.xml'
+VALID_CONFIG_WITH_CATEGORIES = {
+    'platform': 'geo_rss_events',
+    geo_rss_events.CONF_URL: URL,
+    geo_rss_events.CONF_CATEGORIES: [
+        'Category 1',
+        'Category 2'
+    ]
+}
+VALID_CONFIG_WITHOUT_CATEGORIES = {
+    'platform': 'geo_rss_events',
+    geo_rss_events.CONF_URL: URL
+}
+
+
+class TestGeoRssServiceUpdater(unittest.TestCase):
+    """Test the GeoRss service updater."""
+
+    def setUp(self):
+        """Initialize values for this testcase class."""
+        self.hass = get_test_home_assistant()
+        self.config = VALID_CONFIG_WITHOUT_CATEGORIES
+
+    def tearDown(self):
+        """Stop everything that was started."""
+        self.hass.stop()
+
+    def test_setup_with_categories(self):
+        """Test the general setup of this sensor."""
+        self.config = VALID_CONFIG_WITH_CATEGORIES
+        self.assertTrue(
+            setup_component(self.hass, 'sensor', {'sensor': self.config}))
+        self.assertIsNotNone(
+            self.hass.states.get('sensor.event_service_category_1'))
+        self.assertIsNotNone(
+            self.hass.states.get('sensor.event_service_category_2'))
+
+    def test_setup_without_categories(self):
+        """Test the general setup of this sensor."""
+        self.assertTrue(
+            setup_component(self.hass, 'sensor', {'sensor': self.config}))
+        self.assertIsNotNone(self.hass.states.get('sensor.event_service_any'))
+
+    def setup_data(self, url='url'):
+        """Set up data object for use by sensors."""
+        home_latitude = -33.865
+        home_longitude = 151.209444
+        radius_in_km = 500
+        data = geo_rss_events.GeoRssServiceData(home_latitude,
+                                                home_longitude, url,
+                                                radius_in_km)
+        return data
+
+    def test_update_sensor_with_category(self):
+        """Test updating sensor object."""
+        raw_data = load_fixture('geo_rss_events.xml')
+        # Loading raw data from fixture and plug in to data object as URL
+        # works since the third-party feedparser library accepts a URL
+        # as well as the actual data.
+        data = self.setup_data(raw_data)
+        category = "Category 1"
+        name = "Name 1"
+        unit_of_measurement = "Unit 1"
+        sensor = geo_rss_events.GeoRssServiceSensor(category,
+                                                    data, name,
+                                                    unit_of_measurement)
+
+        sensor.update()
+        assert sensor.name == "Name 1 Category 1"
+        assert sensor.unit_of_measurement == "Unit 1"
+        assert sensor.icon == "mdi:alert"
+        assert len(sensor._data.events) == 4
+        assert sensor.state == 1
+        assert sensor.device_state_attributes == {'Title 1': "117km"}
+        # Check entries of first hit
+        assert sensor._data.events[0][geo_rss_events.ATTR_TITLE] == "Title 1"
+        assert sensor._data.events[0][
+                   geo_rss_events.ATTR_CATEGORY] == "Category 1"
+        self.assertAlmostEqual(sensor._data.events[0][
+                                   geo_rss_events.ATTR_DISTANCE], 116.586, 0)
+
+    def test_update_sensor_without_category(self):
+        """Test updating sensor object."""
+        raw_data = load_fixture('geo_rss_events.xml')
+        data = self.setup_data(raw_data)
+        category = None
+        name = "Name 2"
+        unit_of_measurement = "Unit 2"
+        sensor = geo_rss_events.GeoRssServiceSensor(category,
+                                                    data, name,
+                                                    unit_of_measurement)
+
+        sensor.update()
+        assert sensor.name == "Name 2 Any"
+        assert sensor.unit_of_measurement == "Unit 2"
+        assert sensor.icon == "mdi:alert"
+        assert len(sensor._data.events) == 4
+        assert sensor.state == 4
+        assert sensor.device_state_attributes == {'Title 1': "117km",
+                                                  'Title 2': "302km",
+                                                  'Title 3': "204km",
+                                                  'Title 6': "48km"}
+
+    def test_update_sensor_without_data(self):
+        """Test updating sensor object."""
+        data = self.setup_data()
+        category = None
+        name = "Name 3"
+        unit_of_measurement = "Unit 3"
+        sensor = geo_rss_events.GeoRssServiceSensor(category,
+                                                    data, name,
+                                                    unit_of_measurement)
+
+        sensor.update()
+        assert sensor.name == "Name 3 Any"
+        assert sensor.unit_of_measurement == "Unit 3"
+        assert sensor.icon == "mdi:alert"
+        assert len(sensor._data.events) == 0
+        assert sensor.state == 0
+
+    @mock.patch('feedparser.parse', return_value=None)
+    def test_update_sensor_with_none_result(self, parse_function):
+        """Test updating sensor object."""
+        data = self.setup_data("http://invalid.url/")
+        category = None
+        name = "Name 4"
+        unit_of_measurement = "Unit 4"
+        sensor = geo_rss_events.GeoRssServiceSensor(category,
+                                                    data, name,
+                                                    unit_of_measurement)
+
+        sensor.update()
+        assert sensor.name == "Name 4 Any"
+        assert sensor.unit_of_measurement == "Unit 4"
+        assert sensor.state == 0
diff --git a/tests/fixtures/geo_rss_events.xml b/tests/fixtures/geo_rss_events.xml
new file mode 100644
index 0000000000000000000000000000000000000000..212994756d29e7f211ad125ae2e66ca03f0757a6
--- /dev/null
+++ b/tests/fixtures/geo_rss_events.xml
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="utf-8"?>
+<rss version="2.0" xmlns:georss="http://www.georss.org/georss"
+     xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#">
+    <channel>
+        <!-- Entry within vicinity of home coordinates - Point -->
+        <item>
+            <title>Title 1</title>
+            <description>Description 1</description>
+            <category>Category 1</category>
+            <pubDate>Sun, 30 Jul 2017 09:00:00 UTC</pubDate>
+            <guid>GUID 1</guid>
+            <georss:point>-32.916667 151.75</georss:point>
+        </item>
+        <!-- Entry within vicinity of home coordinates - Point -->
+        <item>
+            <title>Title 2</title>
+            <description>Description 2</description>
+            <category>Category 2</category>
+            <pubDate>Sun, 30 Jul 2017 09:05:00 GMT</pubDate>
+            <guid>GUID 2</guid>
+            <geo:long>148.601111</geo:long>
+            <geo:lat>-32.256944</geo:lat>
+        </item>
+        <!-- Entry within vicinity of home coordinates - Polygon -->
+        <item>
+            <title>Title 3</title>
+            <description>Description 3</description>
+            <category>Category 3</category>
+            <pubDate>Sun, 30 Jul 2017 09:05:00 GMT</pubDate>
+            <guid>GUID 3</guid>
+            <georss:polygon>
+                -33.283333 149.1
+                -33.2999997 149.1
+                -33.2999997 149.1166663888889
+                -33.283333 149.1166663888889
+                -33.283333 149.1
+            </georss:polygon>
+        </item>
+        <!-- Entry out of vicinity of home coordinates - Point -->
+        <item>
+            <title>Title 4</title>
+            <description>Description 4</description>
+            <category>Category 4</category>
+            <pubDate>Sun, 30 Jul 2017 09:15:00 GMT</pubDate>
+            <guid>GUID 4</guid>
+            <georss:point>52.518611 13.408333</georss:point>
+        </item>
+        <!-- Entry without coordinates -->
+        <item>
+            <title>Title 5</title>
+            <description>Description 5</description>
+            <category>Category 5</category>
+            <pubDate>Sun, 30 Jul 2017 09:20:00 GMT</pubDate>
+            <guid>GUID 5</guid>
+        </item>
+        <!-- Entry within vicinity of home coordinates -->
+        <!-- Link instead of GUID; updated instead of pubDate -->
+        <item>
+            <title>Title 6</title>
+            <description>Description 6</description>
+            <category>Category 6</category>
+            <updated>2017-07-30T09:25:00.000Z</updated>
+            <link>Link 6</link>
+            <georss:point>-33.75801 150.70544</georss:point>
+        </item>
+        <!-- Entry with unsupported geometry - Line -->
+        <item>
+            <title>Title 1</title>
+            <description>Description 1</description>
+            <category>Category 1</category>
+            <pubDate>Sun, 30 Jul 2017 09:00:00 UTC</pubDate>
+            <guid>GUID 1</guid>
+            <georss:line>45.256 -110.45 46.46 -109.48 43.84 -109.86</georss:line>
+        </item>
+    </channel>
+</rss>
\ No newline at end of file