-module(pollution). -author("Arkadiusz Cwikla"). %% API -export([createMonitor/0, addStation/3, addValue/5, removeValue/4, getOneValue/4, getStationMean/3, getDailyMean/3, getDailyMaxMean/3, getDailyMinMean/3, getMinimumDistanceStations/3]). %% records -record(measurement, {type, value, date}). -record(monitor, {stations, measurements}). %%createMonitor/0 - tworzy i zwraca nowy monitor zanieczyszczeń; %%addStation/3 - dodaje do monitora wpis o nowej stacji pomiarowej (nazwa i współrzędne geograficzne), zwraca zaktualizowany monitor; %%addValue/5 - dodaje odczyt ze stacji (współrzędne geograficzne lub nazwa stacji, data, typ pomiaru, wartość), zwraca zaktualizowany monitor; %%removeValue/4 - usuwa odczyt ze stacji (współrzędne geograficzne lub nazwa stacji, data, typ pomiaru), zwraca zaktualizowany monitor; %%getOneValue/4 - zwraca wartość pomiaru o zadanym typie, z zadanej daty i stacji; %%getStationMean/3 - zwraca średnią wartość parametru danego typu z zadanej stacji; %%getDailyMean/3 - zwraca średnią wartość parametru danego typu, danego dnia na wszystkich stacjach; %% monitor to krotka slownikow coordinates - name oraz name - lista measurement createMonitor() -> #monitor{stations = dict:new(), measurements = dict:new()}. %% funkcje pomocnicze do sprawdzania warunkow coordinatesUsed(Coordinates, #monitor{stations = Stations}) -> dict:is_key(Coordinates, Stations). nameUsed(StationName, #monitor{measurements = Measurements}) -> dict:is_key(StationName, Measurements). %% sprawdzanie spojnosci danych i ewentualne dodawanie stacji addStation(StationName, Coordinates, Monitor) -> case {coordinatesUsed(Coordinates, Monitor), nameUsed(StationName, Monitor)} of {true, true} -> %% io:format("~p ~n", ["Coordinates and station name are already used"]), Monitor; {true, false} -> %% io:format("~p ~n", ["Coordinates are already used"]), Monitor; {false, true} -> %% io:format("~p ~n", ["Station name is already used"]), Monitor; {false, false} -> addNewStation(StationName, Coordinates, Monitor) end . %% dodawanie stacji addNewStation(StationName, Coordinates, #monitor{stations = Stations, measurements = Measurements}) -> NewStations = dict:store(Coordinates, StationName, Stations), NewMeasurements = dict:append_list(StationName, [], Measurements), %% io:format("~p ~n", ["Added new station"]), #monitor{stations = NewStations, measurements = NewMeasurements} . %% obsluga dodawania wartosci w zaleznosci od tego, czy podano nazwe czy wspolrzedne addValue(StationName, Date, Type, Value, Monitor) when is_list(StationName) -> case nameUsed(StationName, Monitor) of false -> %% io:format("~p ~n", ["Incorrect station name"]), Monitor; true -> addNewValueUsingName(StationName, Date, Type, Value, Monitor) end; addValue(StationCoords, Date, Type, Value, Monitor) when is_tuple(StationCoords) -> case coordinatesUsed(StationCoords, Monitor) of false -> %% io:format("~p ~n", ["Incorrect coordinates"]), Monitor; true -> addNewValueUsingCoords(StationCoords, Date, Type, Value, Monitor) end . %% funkcja pomocnicza getStationName(StationCoords, #monitor{stations = Stations}) -> dict:fetch(StationCoords, Stations) . %% owijka dla przypadku uzycia wspolrzednych addNewValueUsingCoords(StationCoords, Date, Type, Value, Monitor) -> StationName = getStationName(StationCoords, Monitor), addNewValueUsingName(StationName, Date, Type, Value, Monitor) . %% dodawanie wartosci addNewValueUsingName(StationName, Date, Type, Value, Monitor) -> #monitor{measurements = Measurements} = Monitor, StoredMeasurements = dict:fetch(StationName, Measurements), case checkMeasurement(Date, Type, StoredMeasurements) of {_, true} -> %% io:format("~p ~n", ["Added new measurement"]), Measurement = #measurement{type = Type, value = Value, date = Date}, NewMeasurements = dict:append(StationName, Measurement, Measurements), Monitor#monitor{measurements = NewMeasurements}; {_, false} -> %% io:format("~p ~n", ["You cannot add the same measurement twice"]), Monitor end . %% wazna funkcja pomocnicza, sprawdza czy pomiar o danej dacie i typie juz istnieje w danej stacji %% zwraca pusta tablice i true, gdy nie istnieje %% zwraca tablice z istniejacym pomiarem i false, gdy istnieje checkMeasurement(Date, Type, StoredMeasurements) -> EqualDateAndType = fun(#measurement{date = StoredDate, type = StoredType}) -> (StoredDate == Date) and (StoredType == Type) end, FilteredMeasurements = lists:filter(EqualDateAndType, StoredMeasurements), case FilteredMeasurements of [] -> {[], true}; [Measurement] -> {[Measurement], false} end . %% obsluga usuwania wartosci, w zaleznosci od tego, czy podano nazwe czy wspolrzedne removeValue(StationName, Date, Type, Monitor) when is_list(StationName) -> case nameUsed(StationName, Monitor) of false -> %% io:format("~p ~n", ["Incorrect station name"]), Monitor; true -> removeValueUsingName(StationName, Date, Type, Monitor) end; removeValue(StationCoords, Date, Type, Monitor) when is_tuple(StationCoords) -> case coordinatesUsed(StationCoords, Monitor) of false -> %% io:format("~p ~n", ["Incorrect coordinates"]), Monitor; true -> removeValueUsingCoords(StationCoords, Date, Type, Monitor) end . %% owijka dla przypadku uzycia wspolrzednych removeValueUsingCoords(StationCoords, Date, Type, Monitor) -> StationName = getStationName(StationCoords, Monitor), removeValueUsingName(StationName, Date, Type, Monitor) . %% usuwanie pomiaru z danej stacji removeValueUsingName(StationName, Date, Type, Monitor) -> #monitor{measurements = Measurements} = Monitor, StoredMeasurements = dict:fetch(StationName, Measurements), {Measurement, _} = checkMeasurement(Date, Type, StoredMeasurements), NewMeasurements = dict:store(StationName, StoredMeasurements -- Measurement, Measurements), %% io:format("~p ~n", ["Removed measurement"]), Monitor#monitor{measurements = NewMeasurements} . %% obsluga czytania wartosci, w zaleznosci od tego, czy podano nazwe czy wspolrzedne getOneValue(StationName, Date, Type, Monitor) when is_list(StationName) -> case nameUsed(StationName, Monitor) of false -> %% io:format("~p ~n", ["Incorrect station name"]), Monitor; true -> getValueUsingName(StationName, Date, Type, Monitor) end; getOneValue(StationCoords, Date, Type, Monitor) when is_tuple(StationCoords) -> case coordinatesUsed(StationCoords, Monitor) of false -> %% io:format("~p ~n", ["Incorrect coordinates"]), Monitor; true -> getValueUsingCoords(StationCoords, Date, Type, Monitor) end . %% owijka dla przypadku uzycia wspolrzednych getValueUsingCoords(StationCoords, Date, Type, Monitor) -> StationName = getStationName(StationCoords, Monitor), getValueUsingName(StationName, Date, Type, Monitor) . %% czytanie wartosci getValueUsingName(StationName, Date, Type, Monitor) -> #monitor{measurements = Measurements} = Monitor, StoredMeasurements = dict:fetch(StationName, Measurements), case checkMeasurement(Date, Type, StoredMeasurements) of {[], true} -> %% io:format("~p ~n", ["No such value"]), no_value; {[Measurement], false} -> #measurement{value = Value} = Measurement, Value end . %% obliczanie sredniej dla stacji, w zaleznosci od tego, czy podano nazwe czy wspolrzedne getStationMean(StationName, Type, Monitor) when is_list(StationName) -> case nameUsed(StationName, Monitor) of false -> %% io:format("~p ~n", ["Incorrect station name"]), Monitor; true -> getStationMeanUsingName(StationName, Type, Monitor) end; getStationMean(StationCoords, Type, Monitor) when is_tuple(StationCoords) -> case coordinatesUsed(StationCoords, Monitor) of false -> %% io:format("~p ~n", ["Incorrect coordinates"]), Monitor; true -> getStationMeanUsingCoords(StationCoords, Type, Monitor) end . %% owijka dla przypadku uzycia wspolrzednych getStationMeanUsingCoords(StationCoords, Type, Monitor) -> StationName = getStationName(StationCoords, Monitor), getStationMeanUsingName(StationName, Type, Monitor) . %% obliczenie sredniej dla stacji getStationMeanUsingName(StationName, Type, Monitor) -> #monitor{measurements = Measurements} = Monitor, StoredMeasurements = dict:fetch(StationName, Measurements), MatchingTypes = fun(#measurement{type = StoredType}) -> (StoredType == Type) end, FilteredMeasurements = lists:filter(MatchingTypes, StoredMeasurements), getMean(FilteredMeasurements) . %% obliczanie sredniej dziennej dla typu getDailyMean(Type, {Date, _Hour}, Monitor) -> #monitor{measurements = Measurements} = Monitor, Concat = fun(_, Measurement, Acc) -> Acc ++ Measurement end, ConcatenatedMeasurements = dict:fold(Concat, [], Measurements), MatchingTypesAndDates = fun(#measurement{type = StoredType, date = {StoredDate, _StoredHour}}) -> (StoredType == Type) and (StoredDate == Date) end, FilteredMeasurements = lists:filter(MatchingTypesAndDates, ConcatenatedMeasurements), getMean(FilteredMeasurements) . %% pomocnicza funkcja do obliczania srednich getMean(Measurements) -> SumValuesAndOccurrences = fun(#measurement{value = Value}, {Sum, Occurrences}) -> {Sum + Value, Occurrences + 1} end, {Sum, Occurrences} = lists:foldl(SumValuesAndOccurrences, {0, 0}, Measurements), %% io:format("~w ~w ~n", [Sum, Occurrences]), Sum / Occurrences . %% dodatkowe funckje %% getDailyMaxMean/3 - wyszukuje maksymalna srednia wartosc parametru dla danego dnia i zwraca krotke {nazwa, wartosc} %% getDailyMinMean/3 - wyszukuje minimalna srednia wartosc parametru dla danego dnia i zwraca krotke {nazwa, wartosc} getDailyMaxMean(Type, {Date, _Hour}, Monitor) -> Means = getDailyMeans(Type, Date, Monitor), MaxMean = fun(Name, Value, {_MaxName, MaxValue}) when (Value > MaxValue) -> {Name, Value}; (_Name, Value, {MaxName, MaxValue}) when not(Value > MaxValue) -> {MaxName, MaxValue} end, dict:fold(MaxMean, {"", -999999999}, Means) . getDailyMinMean(Type, {Date, _Hour}, Monitor) -> Means = getDailyMeans(Type, Date, Monitor), MinMean = fun(Name, Value, {_MinName, MinValue}) when (Value < MinValue) -> {Name, Value}; (_Name, Value, {MinName, MinValue}) when not(Value < MinValue) -> {MinName, MinValue} end, dict:fold(MinMean, {"", 999999999}, Means) . %% pomocnicza funkcja getDailyMeans(Type, Date, Monitor) -> #monitor{measurements = StoredMeasurements} = Monitor, %% io:format("~s ~n ~w ~n", ["StoredMeasurements", StoredMeasurements]), MatchingTypesAndDates = fun(#measurement{type = StoredType, date = {StoredDate, _StoredHour}}) -> (StoredType == Type) and (StoredDate == Date) end, MapFilteredLists = fun(_, Measurements) -> length(lists:filter(MatchingTypesAndDates, Measurements)) > 0 end, FilteredMeasurements = dict:filter(MapFilteredLists, StoredMeasurements), MapMeans = fun(_, Measurements) -> getMean(Measurements) end, %% io:format("~s ~n ~w ~n", ["FilteredMeasurements", FilteredMeasurements]), dict:map(MapMeans, FilteredMeasurements) . % trzecia dodatkowa funckja z excela % zwraca liste koordynatów stacji które są w odpowiedniej odleglosci od danego punktu getMinimumDistanceStations(Coords, Distance, #monitor{stations = Stations}) -> CoordsList = dict:to_list(Stations), InDistance = fun ({CurrentCoords, _Name}) -> distance(CurrentCoords, Coords) < Distance end, Filtered = lists:filter(InDistance, CoordsList), lists:map(fun ({CurrentCoords, _Name})-> CurrentCoords end, Filtered) . %% haversin zakoszony z forum distance({Lng1, Lat1}, {Lng2, Lat2}) -> Deg2rad = fun(Deg) -> math:pi()*Deg/180 end, [RLng1, RLat1, RLng2, RLat2] = [Deg2rad(Deg) || Deg <- [Lng1, Lat1, Lng2, Lat2]], DLon = RLng2 - RLng1, DLat = RLat2 - RLat1, A = math:pow(math:sin(DLat/2), 2) + math:cos(RLat1) * math:cos(RLat2) * math:pow(math:sin(DLon/2), 2), C = 2 * math:asin(math:sqrt(A)), %% suppose radius of Earth is 6372.8 km Km = 6372.8 * C, Km.