diff --git a/lib/teslamate/locations.ex b/lib/teslamate/locations.ex index 4198155e..c05e96c4 100644 --- a/lib/teslamate/locations.ex +++ b/lib/teslamate/locations.ex @@ -10,7 +10,8 @@ defmodule TeslaMate.Locations do alias __MODULE__.{Address, Geocoder, GeoFence, Cache} alias TeslaMate.Log.{Drive, Position, ChargingProcess} - alias TeslaMate.Repo + alias TeslaMate.Settings.GlobalSettings + alias TeslaMate.{Repo, Settings} def child_spec(_arg) do %{id: __MODULE__, start: {Cachex, :start_link, [Cache, [limit: 100]]}} @@ -36,8 +37,9 @@ defmodule TeslaMate.Locations do end) def find_address(%{latitude: lat, longitude: lng}) do - with {:ok, %{place_id: place_id} = attrs} <- @geocoder.reverse_lookup(lat, lng) do - case Repo.get_by(Address, place_id: place_id) do + with %GlobalSettings{language: lang} <- Settings.get_global_settings!(), + {:ok, %{osm_id: id, osm_type: type} = attrs} <- @geocoder.reverse_lookup(lat, lng, lang) do + case Repo.get_by(Address, osm_id: id, osm_type: type) do %Address{} = address -> {:ok, address} nil -> create_address(attrs) end diff --git a/lib/teslamate/locations/address.ex b/lib/teslamate/locations/address.ex index b926c9b6..214b86d6 100644 --- a/lib/teslamate/locations/address.ex +++ b/lib/teslamate/locations/address.ex @@ -12,7 +12,8 @@ defmodule TeslaMate.Locations.Address do field :longitude, :float field :name, :string field :neighbourhood, :string - field :place_id, :integer + field :osm_id, :integer + field :osm_type, :string field :postcode, :string field :raw, :map field :road, :string @@ -27,7 +28,8 @@ defmodule TeslaMate.Locations.Address do address |> cast(attrs, [ :display_name, - :place_id, + :osm_id, + :osm_type, :latitude, :longitude, :name, @@ -44,11 +46,12 @@ defmodule TeslaMate.Locations.Address do ]) |> validate_required([ :display_name, - :place_id, + :osm_id, + :osm_type, :latitude, :longitude, :raw ]) - |> unique_constraint(:place_id) + |> unique_constraint(:osm_id, name: :addresses_osm_id_osm_type_index) end end diff --git a/lib/teslamate/locations/geocoder.ex b/lib/teslamate/locations/geocoder.ex index 03ebdc00..7c24e8eb 100644 --- a/lib/teslamate/locations/geocoder.ex +++ b/lib/teslamate/locations/geocoder.ex @@ -1,7 +1,8 @@ defmodule TeslaMate.Locations.Geocoder do alias Mojito.{Response, Error} + alias TeslaMate.Locations.Address - def reverse_lookup(lat, lon, lang \\ :en) do + def reverse_lookup(lat, lon, lang \\ "en") do with {:ok, address_raw} <- fetch("https://nominatim.openstreetmap.org/reverse", lang, format: :jsonv2, @@ -90,7 +91,8 @@ defmodule TeslaMate.Locations.Geocoder do defp into_address(raw) do %{ display_name: Map.get(raw, "display_name"), - place_id: Map.get(raw, "place_id"), + osm_id: Map.get(raw, "osm_id"), + osm_type: Map.get(raw, "osm_type"), latitude: Map.get(raw, "lat"), longitude: Map.get(raw, "lon"), name: Map.get(raw, "name"), @@ -103,7 +105,7 @@ defmodule TeslaMate.Locations.Geocoder do state: raw["address"] |> get_first(["state", "province", "state_code"]), state_district: get_in(raw, ["address", "state_district"]), country: raw["address"] |> get_first(["country", "country_name"]), - raw: raw["address"] + raw: raw } end diff --git a/lib/teslamate/repair.ex b/lib/teslamate/repair.ex index f7688ded..6d59ab56 100644 --- a/lib/teslamate/repair.ex +++ b/lib/teslamate/repair.ex @@ -1,16 +1,17 @@ defmodule TeslaMate.Repair do - use GenServer + use GenStateMachine require Logger import Ecto.Query alias TeslaMate.Log.{Drive, Position, ChargingProcess} + alias TeslaMate.Locations.{Address, Geocoder} alias TeslaMate.{Repo, Locations} # API def start_link(opts) do - GenServer.start_link(__MODULE__, opts, name: __MODULE__) + GenStateMachine.start_link(__MODULE__, opts, name: __MODULE__) end # Callbacks @@ -19,17 +20,18 @@ defmodule TeslaMate.Repair do def init(_opts) do {:ok, _ref} = :timer.send_interval(:timer.hours(6), self(), :repair) - send(self(), :repair) - - {:ok, nil} + {:ok, :ready, nil, {:next_event, :internal, :repair}} end + ## Repair + @impl true - def handle_info(:repair, state) do + def handle_event(event, :repair, :ready, _data) when event in [:internal, :info] do from(d in Drive, where: (is_nil(d.start_address_id) or is_nil(d.end_address_id)) and (not is_nil(d.start_position_id) and not is_nil(d.end_position_id)), + order_by: [desc: :id], preload: [:start_position, :end_position] ) |> Repo.all() @@ -37,12 +39,21 @@ defmodule TeslaMate.Repair do from(c in ChargingProcess, where: is_nil(c.address_id) and not is_nil(c.position_id), + order_by: [desc: :id], preload: [:position] ) |> Repo.all() |> repair() - {:noreply, state} + :keep_state_and_data + end + + def handle_event(_kind, :repair, _state, _data) do + :keep_state_and_data + end + + def handle_event(:info, {ref, _result}, _state, _data) when is_reference(ref) do + :keep_state_and_data end # Private @@ -73,8 +84,6 @@ defmodule TeslaMate.Repair do {:ok, _entity} -> Logger.info("OK") end - Process.sleep(3000) - repair(rest) end @@ -83,6 +92,8 @@ defmodule TeslaMate.Repair do defp get_address_id(%Position{} = position) do case :fuse.ask(:addr_fuse, :sync) do :ok -> + Process.sleep(1500) + case Locations.find_address(position) do {:error, reason} -> :fuse.melt(:addr_fuse) @@ -101,10 +112,12 @@ defmodule TeslaMate.Repair do :fuse.install( :addr_fuse, - {{:standard, 2, :timer.minutes(3)}, {:reset, :timer.minutes(15)}} + {{:standard, 5, :timer.minutes(3)}, {:reset, :timer.minutes(15)}} ) get_address_id(position) end end + + defp schedule_refresh(n), do: {:state_timeout, :timer.seconds(n), :refresh} end diff --git a/priv/repo/migrations/20200120142602_replace_place_id_with_osmid.exs b/priv/repo/migrations/20200120142602_replace_place_id_with_osmid.exs new file mode 100644 index 00000000..40ae4661 --- /dev/null +++ b/priv/repo/migrations/20200120142602_replace_place_id_with_osmid.exs @@ -0,0 +1,20 @@ +defmodule TeslaMate.Repo.Migrations.ReplacePlaceIdWithOsmid do + use Ecto.Migration + + def change do + drop(unique_index(:addresses, :place_id)) + + alter table(:addresses) do + remove(:place_id, :integer) + add(:osm_id, :bigint) + add(:osm_type, :text) + end + + create(unique_index(:addresses, [:osm_id, :osm_type])) + + execute("UPDATE drives SET start_address_id = NULL, end_address_id = NULL;", fn -> :ok end) + execute("UPDATE charging_processes SET address_id = NULL;", fn -> :ok end) + execute("DELETE FROM addresses;", fn -> :ok end) + execute("ALTER SEQUENCE addresses_id_seq RESTART;", fn -> :ok end) + end +end diff --git a/test/support/mocks/geocoder.ex b/test/support/mocks/geocoder.ex index 383e21b9..3e369bdf 100644 --- a/test/support/mocks/geocoder.ex +++ b/test/support/mocks/geocoder.ex @@ -1,9 +1,9 @@ defmodule GeocoderMock do - def reverse_lookup(99.9, 99.9) do + def reverse_lookup(99.9, 99.9, _lang) do {:error, :induced_error} end - def reverse_lookup(lat, lng) + def reverse_lookup(lat, lng, _lang) when lat in [52.51599, 52.515, 52.514521] and lng in [13.35199, 13.351, 13.350144] do {:ok, %{ @@ -16,7 +16,8 @@ defmodule GeocoderMock do longitude: "13.3501101", name: nil, neighbourhood: "Tiergarten", - place_id: 89_721_012, + osm_id: 89_721_012, + osm_type: "way", postcode: "10787", raw: %{ "city_district" => "Mitte", @@ -34,7 +35,7 @@ defmodule GeocoderMock do }} end - def reverse_lookup(52.394246, 13.542552) do + def reverse_lookup(52.394246, 13.542552, _lang) do {:ok, %{ city: nil, @@ -47,7 +48,8 @@ defmodule GeocoderMock do longitude: "13.5425707", name: "Tesla Store & Service Center Berlin", neighbourhood: "Altglienicke", - place_id: 64_445_009, + osm_id: 64_445_009, + osm_type: "way", postcode: "12526", raw: %{ "car" => "Tesla Store & Service Center Berlin", @@ -66,7 +68,7 @@ defmodule GeocoderMock do }} end - def reverse_lookup(-25.066188, -130.100502) do + def reverse_lookup(-25.066188, -130.100502, _lang) do {:ok, %{ city: "Adamstown", @@ -78,7 +80,8 @@ defmodule GeocoderMock do longitude: "-130.100512324384", name: nil, neighbourhood: nil, - place_id: 246_879_822, + osm_id: 246_879_822, + osm_type: "way", postcode: nil, raw: %{ "country" => "Pitcairn Islands", @@ -91,7 +94,7 @@ defmodule GeocoderMock do }} end - def reverse_lookup(lat, lon) when is_number(lat) and is_number(lon) do + def reverse_lookup(lat, lon, _lang) when is_number(lat) and is_number(lon) do {:ok, %{ city: "Bielefeld", @@ -104,7 +107,8 @@ defmodule GeocoderMock do longitude: "8.52631835353143", name: "Von-der-Recke-Straße", neighbourhood: "Mitte", - place_id: 103_619_766, + osm_id: 103_619_766, + osm_type: "way", postcode: "33602", raw: %{ "city" => "Bielefeld", diff --git a/test/teslamate/locations/addresses_test.exs b/test/teslamate/locations/addresses_test.exs index 60e25f88..f9d42511 100644 --- a/test/teslamate/locations/addresses_test.exs +++ b/test/teslamate/locations/addresses_test.exs @@ -15,7 +15,8 @@ defmodule TeslaMate.LocationsAddressesTest do longitude: 120.5, name: "some name", neighbourhood: "some neighbourhood", - place_id: 42, + osm_id: 42, + osm_type: "way", postcode: "some postcode", raw: %{}, road: "some road", @@ -32,7 +33,8 @@ defmodule TeslaMate.LocationsAddressesTest do longitude: 456.7, name: "some updated name", neighbourhood: "some updated neighbourhood", - place_id: 43, + osm_id: 43, + osm_type: "road", postcode: "some updated postcode", raw: %{}, road: "some updated road", @@ -49,7 +51,8 @@ defmodule TeslaMate.LocationsAddressesTest do longitude: nil, name: nil, neighbourhood: nil, - place_id: nil, + osm_id: nil, + osm_type: nil, postcode: nil, raw: nil, road: nil, @@ -77,7 +80,8 @@ defmodule TeslaMate.LocationsAddressesTest do assert address.longitude == 120.5 assert address.name == "some name" assert address.neighbourhood == "some neighbourhood" - assert address.place_id == 42 + assert address.osm_id == 42 + assert address.osm_type == "way" assert address.postcode == "some postcode" assert address.raw == %{} assert address.road == "some road" @@ -92,7 +96,8 @@ defmodule TeslaMate.LocationsAddressesTest do display_name: ["can't be blank"], latitude: ["can't be blank"], longitude: ["can't be blank"], - place_id: ["can't be blank"], + osm_id: ["can't be blank"], + osm_type: ["can't be blank"], raw: ["can't be blank"] } end @@ -109,7 +114,8 @@ defmodule TeslaMate.LocationsAddressesTest do assert address.longitude == 456.7 assert address.name == "some updated name" assert address.neighbourhood == "some updated neighbourhood" - assert address.place_id == 43 + assert address.osm_id == 43 + assert address.osm_type == "road" assert address.postcode == "some updated postcode" assert address.raw == %{} assert address.road == "some updated road" @@ -128,7 +134,7 @@ defmodule TeslaMate.LocationsAddressesTest do assert {:ok, %Address{} = address} = Locations.find_address(%{latitude: 52.019596, longitude: 8.526318}) - assert address.place_id == 103_619_766 + assert address.osm_id == 103_619_766 assert address.city == "Bielefeld" assert [^address] = Repo.all(Address) diff --git a/test/teslamate/locations/geocoder_test.exs b/test/teslamate/locations/geocoder_test.exs index 041f4a4a..df0e2661 100644 --- a/test/teslamate/locations/geocoder_test.exs +++ b/test/teslamate/locations/geocoder_test.exs @@ -43,22 +43,43 @@ defmodule TeslaMate.Locations.GeocoderTest do longitude: "41.1288167", name: "Kahve Deryası", neighbourhood: "Ziyagökalp Mahallesi", - place_id: 241_575_531, + osm_id: 5_983_038_298, + osm_type: "node", postcode: "72060", - raw: %{ - "cafe" => "Kahve Deryası", - "city" => "Batman merkez", - "country" => "Turkey", - "country_code" => "tr", - "postcode" => "72060", - "residential" => "Batman", - "road" => "Cihan Kavşağı", - "state" => "Southeastern Anatolia Region", - "suburb" => "Ziyagökalp Mahallesi" - }, road: "Cihan Kavşağı", state: "Southeastern Anatolia Region", - state_district: nil + state_district: nil, + raw: %{ + "address" => %{ + "cafe" => "Kahve Deryası", + "city" => "Batman merkez", + "country" => "Turkey", + "country_code" => "tr", + "postcode" => "72060", + "residential" => "Batman", + "road" => "Cihan Kavşağı", + "state" => "Southeastern Anatolia Region", + "suburb" => "Ziyagökalp Mahallesi" + }, + "addresstype" => "amenity", + "boundingbox" => ["37.8894442", "37.8896442", "41.1287167", "41.1289167"], + "category" => "amenity", + "display_name" => + "Kahve Deryası, Cihan Kavşağı, Batman, Ziyagökalp Mahallesi, Batman merkez, Batman, Southeastern Anatolia Region, 72060, Turkey", + "extratags" => %{}, + "importance" => 0, + "lat" => "37.8895442", + "licence" => + "Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright", + "lon" => "41.1288167", + "name" => "Kahve Deryası", + "namedetails" => %{"name" => "Kahve Deryası"}, + "osm_id" => 5_983_038_298, + "osm_type" => "node", + "place_id" => 241_575_531, + "place_rank" => 30, + "type" => "cafe" + } }} end end diff --git a/test/teslamate/log/log_charging_test.exs b/test/teslamate/log/log_charging_test.exs index 62ee65c0..2275713b 100644 --- a/test/teslamate/log/log_charging_test.exs +++ b/test/teslamate/log/log_charging_test.exs @@ -56,7 +56,7 @@ defmodule TeslaMate.LogChargingTest do assert cproc.position.date == @valid_pos_attrs.date assert %DateTime{} = cproc.start_date assert cproc.address.city == "Bielefeld" - assert cproc.address.place_id == 103_619_766 + assert cproc.address.osm_id == 103_619_766 end test "with invalid data returns error changeset" do diff --git a/test/teslamate_web/live/charge_cost_live_test.exs b/test/teslamate_web/live/charge_cost_live_test.exs index 2da2a34e..50ee07ef 100644 --- a/test/teslamate_web/live/charge_cost_live_test.exs +++ b/test/teslamate_web/live/charge_cost_live_test.exs @@ -108,7 +108,8 @@ defmodule TeslaMateWeb.ChargeLive.CostTest do Locations.create_address(%{ display_name: "Beelitz Supercharger, Dr.-Herrmann-Straße, Beelitz-Heilstätten, Beelitz, Potsdam-Mittelmark, Brandenburg, 14547, Deutschland", - place_id: 66_385_359, + osm_id: 66_385_359, + osm_type: "way", latitude: 52.2668097, longitude: 12.9223251, name: "Beelitz Supercharger",