[Custom Integration] POI Zones - Automatic Zones from OpenStreetMap Data

[Custom Integration] POI Zones - Automatic Zones from OpenStreetMap Data

Ever wanted Home Assistant to automatically create zones for all the hospitals, schools, restaurants, or parks near you? Now it can!

What is POI Zones?

POI Zones is a custom integration that automatically creates Home Assistant zones for Points of Interest (POIs) near your location using real OpenStreetMap data. No manual zone creation, no coordinate hunting - just select a POI type and city, and zones are created automatically.

:bullseye: Real-World Example

Here’s what made me create this: I wanted to get notified when family members were at a hospital for more than 30 minutes. Instead of manually creating zones for every hospital in Syracuse, this integration:

  1. Found all hospitals within 25km automatically
  2. Created zones with intelligent sizing (small clinics get 130m radius, major hospitals get 280m)
  3. Enabled an automation that alerts everyone when someone’s at a hospital for 30+ minutes

The best part? The automation template is completely generic - it automatically adapts when zones are added or removed, and works for any city!

:sparkles: Features

40+ POI Types

  • Healthcare: Hospitals, Urgent Care, Clinics, Pharmacies, Dentists, Vets
  • Education: Schools, Universities, Daycares
  • Transportation: Airports, Train Stations, Gas Stations, EV Charging
  • Food & Drink: Restaurants, Fast Food, Cafes, Bars, Grocery Stores
  • Services: Banks, Post Offices, Libraries, Hotels
  • Public Safety: Police Stations, Fire Stations
  • Recreation: Parks, Gyms, Movie Theaters
  • And many more!

Dynamic Sizing

For healthcare facilities, zones are automatically sized based on:

  • :hospital: Number of hospital beds (if available in OSM)
  • :office_building: Building footprint (small clinics vs large complexes)
  • :ambulance: Emergency status
  • Result: Small clinics get 105m zones, major medical centers get 280m zones

Automation Ready

The integration includes generic automation templates that work with any POI type and automatically adapt when zones change. No hardcoded zone names!

# Example: Hospital Visit Alert
# Notifies when anyone is at a hospital/urgent care for 30+ minutes
# Automatically discovers all hospital zones - no hardcoding!

automation:
  - alias: "Hospital Visit Alert"
    trigger:
      - platform: template
        value_template: >
          {% set poi_zones = namespace(ids=[]) %}
          {% for sensor in states.sensor | selectattr('attributes.poi_type', 'defined') %}
            {% if sensor.attributes.poi_type in ['hospital', 'urgent_care'] %}
              {% set poi_zones.ids = poi_zones.ids + sensor.attributes.zone_ids %}
            {% endif %}
          {% endfor %}
          {{ expand(states.person)
             | selectattr('state', 'in', poi_zones.ids)
             | list | count > 0 }}
        for:
          minutes: 30
    action:
      - service: notify.notify
        data:
          title: "🏥 Healthcare Visit Alert"
          message: >
            {% set poi_zones = namespace(ids=[]) %}
            {% for sensor in states.sensor | selectattr('attributes.poi_type', 'defined') %}
              {% if sensor.attributes.poi_type in ['hospital', 'urgent_care'] %}
                {% set poi_zones.ids = poi_zones.ids + sensor.attributes.zone_ids %}
              {% endif %}
            {% endfor %}
            {% set people = expand(states.person) | selectattr('state', 'in', poi_zones.ids) | list %}
            {% for person in people %}
              {{ person.name }} has been at {{ state_attr('zone.' + person.state, 'friendly_name') }} for over 30 minutes.
            {% endfor %}

:package: Installation

Via HACS (Recommended)

  1. Open HACS
  2. Click the 3 dots (⋮) → “Custom repositories”
  3. Add: https://github.com/DEADSEC-SECURITY/ha-poi-zones
  4. Category: Integration
  5. Search for “POI Zones” and install
  6. Restart Home Assistant

Configuration

  1. Go to Settings → Devices & Services
  2. Click “Add Integration” → Search “POI Zones”
  3. Enter:
    • City: Your city (e.g., “Syracuse, NY”)
    • POI Type: Choose from 40+ types
    • Search Radius (optional): Distance in km
    • Zone Radius (optional): Override default sizing

:counterclockwise_arrows_button: How It Works

  1. Geocodes your city using Nominatim
  2. Queries OpenStreetMap via Overpass API for POIs
  3. Creates HA zones automatically with intelligent sizing
  4. Updates weekly to catch new POIs
  5. Provides sensor data with all POI details for automations

:light_bulb: Use Cases

Safety & Awareness

  • Alert when kids arrive at/leave school
  • Notify when elderly parent visits hospital
  • Track time spent at doctor appointments

Convenience

  • “Turn on lights when approaching home depot”
  • “Start coffee when near favorite cafe”
  • “Remind me to pick up prescription when near pharmacy”

Automation Ideas

  • School zones → Adjust quiet hours
  • Restaurant zones → Suggest splitting bills
  • Gas station zones → Log fuel stops
  • Park zones → Enable outdoor mode

:bar_chart: What You Get

Each integration instance creates:

  • Sensor entity showing POI count and details
  • Zone entities for each discovered POI
  • Sensor attributes with full POI data:
    • Names, addresses, coordinates
    • Phone numbers, websites, hours
    • Zone IDs for easy automation
    • Additional metadata (beds, cuisine, etc.)

:globe_showing_europe_africa: Data Source

All data comes from OpenStreetMap contributors (ODbL license):

  • Free and open
  • Community maintained
  • Updated weekly
  • Works worldwide

:handshake: Feedback Wanted!

This is the initial release (v1.0.0). I’d love feedback on:

  • Which POI types you find most useful
  • Automation ideas and templates
  • Zone sizing preferences
  • Feature requests
  • Bug reports

:books: Links

:folded_hands: Credits

Data provided by OpenStreetMap contributors. Integration inspired by the need to automate what we were doing manually - creating zones for every hospital, school, and restaurant in town!


Installation: Add https://github.com/DEADSEC-SECURITY/ha-poi-zones as a HACS custom repository

Looking forward to hearing how you use it! What POI types would you want to see added?

1 Like

This is great. One thing that’s missing is a sports entry for ball fields, pools, etc. Can you add that?

Right now I’m just recording time spent at certain places. I’m just curious how much time I waste at some places.

This will list all the zones you’ve ever been in (retained in data):

SELECT DISTINCT s.state 
FROM states s
JOIN states_meta m ON s.metadata_id = m.metadata_id
WHERE m.entity_id = 'person.your_name';

And this will tell you the total time spent at any one place:

SELECT 
    SUM(end_time - start_time) / 3600 AS total_hours
FROM (
    SELECT 
        s.state,
        s.last_updated_ts AS start_time,
        LEAD(s.last_updated_ts) OVER (PARTITION BY s.metadata_id ORDER BY s.last_updated_ts) AS end_time
    FROM states s
    JOIN states_meta m ON s.metadata_id = m.metadata_id
    WHERE m.entity_id = 'person.your_name'
) AS segments
WHERE state = 'home' 
  AND end_time IS NOT NULL;

Its a cool integration. I look forward to other people’s ideas too.

I enabled Gyms & Fitness Centers and that seems to cover the sports places I was looking for (pools since my son plays waterpolo). Not sure if ball fields are covered.

Also - and I know this is not under your control - but any idea why one grocery store shows up and another doesn’t? I mean, Aldi shows up but Lidl doesn’t. They’re basically the same store. I don’t know how these things are categorized.

OK, this is pretty cool. You might find use for this.

Install the sql integration, then create a new entity called ‘Time in Zone’ or whatever. Put this into the query:

SELECT 
	'Active' AS status,
	JSON_ARRAYAGG(
		JSON_OBJECT(
			'zone_name', zone_name,
			'total_hours', total_hours
		)
	) AS all_data
FROM (
	WITH state_segments AS (
		SELECT 
			s.state,
			s.last_updated_ts AS start_time,
			LEAD(s.last_updated_ts) OVER (PARTITION BY s.metadata_id ORDER BY s.last_updated_ts) AS end_time
		FROM states s
		JOIN states_meta m ON s.metadata_id = m.metadata_id
		WHERE m.entity_id = 'person.your_name'
	)
SELECT 
	state AS zone_name,
	ROUND(SUM(end_time - start_time) / 3600, 1) AS total_hours
FROM state_segments
WHERE end_time IS NOT NULL
GROUP BY state
ORDER BY total_hours DESC
) AS source_data;

Make sure to change person.your_name to your actual person name. For the SELECT box, put status. This query will create a json array that can be used in a markdown card:

type: markdown
content: |
  {% set raw_data = state_attr('sensor.time_in_zone', 'all_data') %} 
  {% if raw_data is not none %}
    {% for row in raw_data | from_json %}
  * {{ row.total_hours }} hours at {{ row.zone_name }}
    {% endfor %}
  {% else %}
    No data found
  {% endif %}

Make sure to change sensor.time_in_zone to your actual sql entity.

I made one card for my wife and one for me. Its cool.

The only issue is that there are zones that I’ve never actually visited, only passed through. I think this can be refined in the query. I’ll try later.

edit:

This query will exclude all zone visits less than 5 minutes in duration.

SELECT 
	'Active' AS status,
	JSON_ARRAYAGG(
		JSON_OBJECT(
			'zone_name', zone_name,
			'total_hours', total_hours
		)
	) AS all_data
FROM (
	WITH state_segments AS (
		SELECT 
			s.state,
			s.last_updated_ts AS start_time,
			LEAD(s.last_updated_ts) OVER (PARTITION BY s.metadata_id ORDER BY s.last_updated_ts) AS end_time
		FROM states s
		JOIN states_meta m ON s.metadata_id = m.metadata_id
		WHERE m.entity_id = 'person.your_name'
	)
	SELECT 
		state AS zone_name,
		ROUND(SUM(end_time - start_time) / 3600, 1) AS total_hours
	FROM state_segments
	WHERE end_time IS NOT NULL 
	AND (end_time - start_time) > 300
	GROUP BY state
	ORDER BY LOWER(state) ASC
) AS source_data;

To change the duration of the exclusion time in seconds: AND (end_time - start_time) > 300. This reduced my list of zones substantially. I have many categories enabled in the POI integration. Before enabling this query, many zones were just passed by or through, resulting in “0.0 hours”.

1 Like

Very likely due to miss categorization on the api I use, I can check though to make sure.

If you don’t mind opening an Issue in GitHub I would appreciate it.