Skip to content

Commit

Permalink
feat: map for entities and multiple map services
Browse files Browse the repository at this point in the history
Why:

 * openstreetmap isn't always the best option.
 * there are more than just configuration with location info.

This change addreses the need by:

 * introduce a `--service` option to let you choose which
   map service to use. currently: openstreetmap, google and bing
   implemented
 * add optional entity parameter to lookup and it has
   latitiude and longitude in its state it will be mapped.
 * bing has extra feature of allowing showing a name/label, here
   `friendly_name` is used otherwise `entity_id`

Example usage:

`hass-cli map person.max`
`hass-cli --service bing device_tracker.maxs_phone`

Future idea: allow to map multiple points, can be done as directions
in google but Bing has better support.
  • Loading branch information
maxandersen committed Feb 22, 2019
1 parent 498d8ea commit 542c0db
Show file tree
Hide file tree
Showing 2 changed files with 106 additions and 10 deletions.
65 changes: 55 additions & 10 deletions homeassistant_cli/plugins/map.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,68 @@
"""Map plugin for Home Assistant CLI (hass-cli)."""
import sys
import webbrowser

import click
import homeassistant_cli.autocompletion as autocompletion
from homeassistant_cli.cli import pass_context
from homeassistant_cli.config import Configuration
import homeassistant_cli.remote as api

OSM_URL = 'https://www.openstreetmap.org'
ZOOM = 17
OSM_URL = "https://www.openstreetmap.org/"
GOOGLE_URL = "https://www.google.com/maps/search/"
BING_URL = "https://www.bing.com/maps"
SERVICE = {
'openstreetmap': OSM_URL + '?mlat={0}&mlon={1}#map=17/{0}/{1}',
'google': GOOGLE_URL + '?api=1&query={0},{1}',
'bing': BING_URL + '?v=2&cp={0}~{1}&lvl=17&sp=point.{0}_{1}_{2}',
}


@click.command('map')
@click.argument( # type: ignore
'entity', required=False, autocompletion=autocompletion.entities
)
@click.option(
'--service', default='openstreetmap', type=click.Choice(SERVICE.keys())
)
@pass_context
def cli(ctx: Configuration) -> None:
"""Print the current location on a map."""
response = api.get_config(ctx)
def cli(ctx: Configuration, service: str, entity: str) -> None:
"""Show the location of the config or an entity on a map."""
latitude = None
longitude = None

if response:
url = '{0}/?mlat={2}&mlon={3}#map={1}/{2}/{3}'.format(
OSM_URL, ZOOM, response.get('latitude'), response.get('longitude')
)
webbrowser.open_new_tab(url)
if entity:
thing = entity
data = api.get_state(ctx, entity)
if data:
attr = data.get('attributes', {})
latitude = attr.get('latitude')
longitude = attr.get('longitude')
thing = attr.get('friendly_name', entity)
else:
thing = "configuration"
response = api.get_config(ctx)
if response:
latitude = response.get('latitude')
longitude = response.get('longitude')
thing = response.get('location_name', thing)

if latitude and longitude:
urlpattern = SERVICE.get(service)
import urllib.parse

if urlpattern:
url = urlpattern.format(
latitude, longitude, urllib.parse.quote_plus(thing)
)
ctx.echo(
"{} location is at {}, {}".format(thing, latitude, longitude)
)
webbrowser.open_new_tab(url)
else:
ctx.echo(
"Could not find url pattern for service {}".format(service)
)
else:
ctx.echo("No exact location info found in {}".format(thing))
sys.exit(2)
51 changes: 51 additions & 0 deletions tests/test_map.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
"""Tests file for hass-cli map."""
from typing import no_type_check
from unittest.mock import patch

from click.testing import CliRunner
import homeassistant_cli.cli as cli
import pytest
import requests_mock


@no_type_check
@pytest.mark.parametrize(
"service,url",
[
("openstreetmap", "https://www.openstreetmap.org"),
("bing", "https://www.bing.com"),
("google", "https://www.google.com"),
],
)
def test_map_services(service, url, default_entities) -> None:
"""Test map feature."""
entity_id = 'zone.school'
school = next(
(x for x in default_entities if x['entity_id'] == entity_id), "ERROR!"
)

print(school)
with requests_mock.Mocker() as mock, patch(
'webbrowser.open_new_tab'
) as mocked_browser:
mock.get(
"http://localhost:8123/api/states/{}".format(entity_id),
json=school,
status_code=200,
)

runner = CliRunner()
result = runner.invoke(
cli.cli,
["--output=json", "map", "--service", service, "zone.school"],
catch_exceptions=False,
)
assert result.exit_code == 0

callurl = mocked_browser.call_args[0][0]

assert callurl.startswith(url)
assert str(school.get('attributes').get('latitude')) in callurl
assert (
str(school.get('attributes').get('longitude')) in callurl
) # typing: ignore

0 comments on commit 542c0db

Please sign in to comment.