diff --git a/libs/car.py b/libs/car.py index 27f1d0b..d3fc416 100644 --- a/libs/car.py +++ b/libs/car.py @@ -38,6 +38,12 @@ class Car: def is_hybrid(self) -> bool: return self.fuel_capacity > 0 and self.battery_power > 0 + def has_battery(self): + return self.battery_power > 0 + + def has_fuel(self): + return self.fuel_capacity > 0 + def get_status(self): if self.status is not None: return self.status diff --git a/libs/charging.py b/libs/charging.py index 50400f0..6b4190b 100644 --- a/libs/charging.py +++ b/libs/charging.py @@ -13,17 +13,9 @@ class Charging: elec_price: ElecPrice = ElecPrice(None) @staticmethod - def get_chargings(mini=None, maxi=None) -> List[dict]: + def get_chargings() -> List[dict]: conn = Database.get_db() - if mini is not None: - if maxi is not None: - res = conn.execute("select * from battery WHERE start_at>=? and start_at<=?", (mini, maxi)).fetchall() - else: - res = conn.execute("select * from battery WHERE start_at>=?", (mini,)).fetchall() - elif maxi is not None: - res = conn.execute("select * from battery WHERE start_at<=?", (maxi,)).fetchall() - else: - res = conn.execute("select * from battery").fetchall() + res = conn.execute("select * from battery ORDER BY start_at").fetchall() conn.close() return list(map(dict, res)) diff --git a/trip.py b/trip.py index 8c6208e..a3bfe75 100644 --- a/trip.py +++ b/trip.py @@ -2,7 +2,6 @@ import logging from statistics import mean from typing import List, Dict -from dateutil import tz from geojson import Feature, FeatureCollection, MultiLineString from libs.car import Cars, Car @@ -38,6 +37,7 @@ class Trip: self.car: Car = None self.altitude_diff = None self.temperatures = [] + self.id = None def add_points(self, latitude, longitude): self.positions.append(Points(latitude, longitude)) @@ -78,19 +78,22 @@ class Trip: "average consumption": self.consumption_km, "average consumption fuel": self.consumption_fuel_km}) - def get_info(self, row_id=None): - res = {"start_at": self.start_at.astimezone(tz.tzlocal()).replace(tzinfo=None).strftime("%x %X"), - # convert to naive tz, - "duration": self.duration * 60, "speed_average": self.speed_average, - "consumption_km": self.consumption_km, "consumption_fuel_km": self.consumption_fuel_km, - "distance": self.distance, "mileage": self.mileage, "altitude_diff": self.altitude_diff} - if row_id is not None: - res["id"] = row_id - return res - def get_consumption(self): - return {"speed": self.speed_average, "consumption_km": self.consumption_km, "start_at": self.start_at, - "consumption_by_temp": self.get_temperature(), "positions": self.get_positions()} + def get_info(self): + + res = {"consumption_km": self.consumption_km, "start_at": self.start_at, + "consumption_by_temp": self.get_temperature(), "positions": self.get_positions(), + "duration": self.duration * 60, "speed_average": self.speed_average, "distance": self.distance, + "mileage": self.mileage, "altitude_diff": self.altitude_diff, "id": self.id, + "consumption": self.consumption + } + if self.car.has_battery(): + res["consumption_km"] = self.consumption_km + + if self.car.has_fuel(): + res["consumption_fuel_km"] = self.consumption_fuel_km + + return res def set_altitude_diff(self, start, end): try: @@ -110,14 +113,14 @@ class Trip: class Trips(list): def __init__(self, *args): list.__init__(self, *args) + self.trip_num = 1 def to_geo_json(self): feature_collection = FeatureCollection(self) return feature_collection - def get_long_trips(self): - res = [trip.get_consumption() for trip in self if trip.consumption > 1.8] - return res + def get_trips_as_dict(self): + return [trip.get_info() for trip in self] def get_distance(self): return self[-1].mileage - self[0].mileage @@ -125,6 +128,8 @@ class Trips(list): def check_and_append(self, trip: Trip): if trip.consumption_km <= trip.car.max_elec_consumption and \ trip.consumption_fuel_km <= trip.car.max_fuel_consumption: + trip.id = self.trip_num + self.trip_num += 1 self.append(trip) return True logger.debugv("trip discarded") @@ -218,11 +223,3 @@ class Trips(list): trips_by_vin[vin] = trips conn.close() return trips_by_vin - - def get_info(self): - res = [] - row_id = 1 - for trip in self: - res.append(trip.get_info(row_id)) - row_id += 1 - return res diff --git a/web/assets/clientside.js b/web/assets/clientside.js index 8aed1f4..d3710bf 100644 --- a/web/assets/clientside.js +++ b/web/assets/clientside.js @@ -1,17 +1,91 @@ -function filter_dataset(data, range, old_figure, x,y) { - function is_in_range(st){ - ts_date = new Date(st).getTime()/1000 - return ts_date >= range[0] && ts_date <= range[1] - } - var data_filtered = data.filter(line => is_in_range(line["start_at"])); +class Avg{ + constructor(){ + this.total = 0; + this.count = 0; + } + add_value(value){ + if (typeof value === 'number') { + this.count++; + this.total = ((this.total*(this.count-1))/this.count) + (value/this.count); + } + } + + average(){ + return this.total; + } + static get_average_key(array, key){ + var avg = new Avg() + array.forEach(function(obj){ avg.add_value(obj[key])}) + return avg.average(); + } +} +var logger = function() +{ + var oldConsoleLog = null; + var pub = {}; + + pub.enableLogger = function enableLogger() + { + if(oldConsoleLog == null) + return; + + window['console']['log'] = oldConsoleLog; + }; + + pub.disableLogger = function disableLogger() + { + oldConsoleLog = console.log; + window['console']['log'] = function() {}; + }; + + return pub; +}(); +function add_date_str(data, date_key){ + var date_option = [undefined, {"hour":"numeric", "minute":"numeric"}] + for([dataset_name, dataset] of Object.entries(data)){ + dataset.forEach(function (row) { + date_key[dataset_name].forEach(function (key) { + var date= new Date(row[key]); + row[key] = date + row[key + "_str"] = date.toLocaleDateString(...date_option); + }) + }) + } +} + +function filter_dataset(data,range){ + function date_from_iso_str(st){ + return new Date(st).getTime()/1000 + } + function is_in_range(st){ + ts_date = date_from_iso_str(st); + return ts_date >= range[0] && ts_date <= range[1] + } + var res = {"trips": data["trips"].filter(line => is_in_range(line["start_at"])), + "chargings": data["chargings"].filter(line => is_in_range(line["start_at"]))}; + console.log("filtered_dataset", res); + return res; +} + + +function filter_short_trip(data){ + var long_trips = {"trips": data["trips"].filter(line => line["distance"]>10), + "chargings": data["chargings"]}; + console.log("long trips:" , long_trips) + return long_trips; +} + +function update_figures(data, old_figure, x,y) { + var trips = data["trips"] var figures = []; var i=0 y.forEach(function (y_label){ var x_label=x[i] var figure = Object.assign({}, old_figure[i]); i++; + // console.log(old_figure[i]); // var unique_y_label = y[i].filter((v, i, a) => a.indexOf(v) === i); - //var data_nonnull = data_filtered + //var data_nonnull = trips // unique_y_label.forEach(function(label) { // data_nonnull = data_nonnull.filter(line => line[label]); // }); @@ -20,7 +94,7 @@ function filter_dataset(data, range, old_figure, x,y) { figure["data"][0]["lon"] = [] figure["data"][0]["hovertext"] = [] var trip = null; - for (trip of data_filtered) { + for (trip of trips) { x_pos = trip["positions"][x_label] figure["data"][0]["lat"].push(...x_pos, null); figure["data"][0]["lon"].push(...trip["positions"][y_label[0]]); @@ -33,15 +107,119 @@ function filter_dataset(data, range, old_figure, x,y) { figure.data[1].lon = [figure.layout.mapbox.center.lon] } else { - x_values = data_filtered.map(a => a[x_label]) + x_values = trips.map(a => a[x_label]) // for each y label for (j = 0; j < y_label.length; j++) { - figure["data"][j]["y"] = data_filtered.map(a => a[y_label[j]]); + figure["data"][j]["y"] = trips.map(a => a[y_label[j]]); figure["data"][j]["x"] = x_values } } - // console.log(figure); + console.log(x_label, figure); figures.push(figure); }); return figures; } + +function update_table(data, tables){ + console.log("tables", tables); + figures = []; + tables.forEach(function (table){ + figures.push(data[table.src]); + }) + return figures; +} + + +function update_cards_value(data){ + res = {} + avg_co2=new Avg(); + avg_kw = new Avg(); + avg_time = new Avg() + avg_price = new Avg(); + data["chargings"].forEach(function(charge){ + diff = ((new Date(charge["stop_at"])) - (new Date(charge["start_at"])))/3600000; + avg_kw.add_value(charge["kw"]); + avg_co2.add_value(charge["co2"]); + avg_price.add_value(charge["price"]); + if(diff > 0){ + avg_time.add_value(diff); + } + }) + total_distance = data["trips"][data["trips"].length-1]["mileage"]-data["trips"][0]["mileage"] + avg_kw = avg_kw.average(); + avg_co2 = avg_co2.average() + avg_price_kw = avg_price.average()/avg_kw; + + res["avg_consum_kw"] = Avg.get_average_key(data["trips"], "consumption_km") + res["avg_emission_kw"] = avg_co2; + res["avg_emission_km"] = res["avg_emission_kw"]*res["avg_consum_kw"]/100; + res["avg_chg_speed"] = avg_kw/avg_time.average() + res["elec_consum_kw"] = total_distance*res["avg_consum_kw"]/100; + res["elec_consum_price"] = avg_price_kw*res["elec_consum_kw"] + res["avg_consum_price"] = avg_price_kw*res["avg_consum_kw"] + //console.log(res); + for (const [key, value] of Object.entries(res)) { + document.getElementById(key).innerHTML=value.toPrecision(3); + } +} + +function sort_dataset(ctx, data, tables){ + var table_id = ctx.prop_id.split(".")[0]; + if(ctx.value.length > 0){ + var asc = ctx.value[0].direction==='asc'; + var column_id = ctx.value[0].column_id; + var table = tables.filter(table => table.table_id === table_id)[0]; + var sorted = null; + if (column_id.endsWith("_str")){ + column_id = column_id.slice(0, -4); + sorted = data[table.src].sort(function(a,b){ + return a[column_id] - b[column_id]; + }); + } + else if(typeof data[table.src][0][column_id] == 'number'){ + sorted = data[table.src].sort(function(a,b){ + return a[column_id] - b[column_id]; + }); + } + else { + sorted = data[table.src].sort((a, b) => a[column_id].localeCompare(b[column_id])); + } + if(asc===false){ + sorted = sorted.reverse(); + } + data[table.src]=sorted; + } +} + + + +function filter_and_sort(data,range, figures, p, log) { + if(log>10){ + logger.disableLogger(); + } + console.log("figures:", figures); + console.log("data:", data) + var ctx = dash_clientside.callback_context.triggered; + console.log("ctx", ctx); + var out_figures = []; + if(ctx.length > 0 && ctx[0].prop_id.endsWith("sort_by")){ + var data_filtered = filter_dataset(data, range); + sort_dataset(ctx[0], data_filtered, p.table_src); + out_figures.push(...update_table(data_filtered, p.table_src)); + out_figures.push(...figures.graph); + out_figures.push(...figures.maps); + } + else{ + add_date_str(data,p.date_columns); + var data_filtered = filter_dataset(data, range); + out_figures.push(...update_table(data_filtered, p.table_src)); + console.log(data_filtered["trips"].length); + long_trips = filter_short_trip(data_filtered); + console.log("trips", data_filtered["trips"].length); + console.log("long_trips", long_trips["trips"].length); + out_figures.push(...update_figures(long_trips, figures["graph"], p.graph_x_label, p.graph_y_label)); + out_figures.push(...update_figures(data_filtered, figures["maps"], p.map_x_label, p.map_y_label)); + update_cards_value(long_trips); + } + return out_figures; +} \ No newline at end of file diff --git a/web/assets/sprites/osm-liberty.json b/web/assets/sprites/osm-liberty.json new file mode 100644 index 0000000..4aae1dd --- /dev/null +++ b/web/assets/sprites/osm-liberty.json @@ -0,0 +1,1745 @@ +{ + "aerialway-11": { + "height": 15, + "pixelRatio": 1, + "width": 15, + "x": 314, + "y": 0 + }, + "aerialway-15": { + "height": 19, + "pixelRatio": 1, + "width": 19, + "x": 122, + "y": 229 + }, + "airfield-11": { + "height": 15, + "pixelRatio": 1, + "width": 15, + "x": 329, + "y": 0 + }, + "airfield-15": { + "height": 19, + "pixelRatio": 1, + "width": 19, + "x": 141, + "y": 229 + }, + "airport-11": { + "height": 15, + "pixelRatio": 1, + "width": 15, + "x": 344, + "y": 0 + }, + "airport-15": { + "height": 19, + "pixelRatio": 1, + "width": 19, + "x": 160, + "y": 229 + }, + "alcohol_shop-11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 485, + "y": 145 + }, + "alcohol_shop-15": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 104, + "y": 94 + }, + "america_football-11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 418, + "y": 166 + }, + "america_football-15": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 117, + "y": 64 + }, + "amusement_park-11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 435, + "y": 166 + }, + "amusement_park-15": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 138, + "y": 64 + }, + "aquarium-11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 452, + "y": 166 + }, + "aquarium-15": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 159, + "y": 64 + }, + "arrow": { + "height": 7, + "pixelRatio": 1, + "width": 20, + "x": 285, + "y": 250 + }, + "art_gallery-11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 469, + "y": 166 + }, + "art_gallery-15": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 180, + "y": 64 + }, + "attraction-11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 486, + "y": 166 + }, + "attraction-15": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 201, + "y": 64 + }, + "bakery-11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 252, + "y": 187 + }, + "bakery-15": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 222, + "y": 64 + }, + "bank-11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 269, + "y": 187 + }, + "bank-15": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 125, + "y": 94 + }, + "bar-11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 286, + "y": 187 + }, + "bar-15": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 146, + "y": 94 + }, + "baseball-11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 303, + "y": 187 + }, + "baseball-15": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 167, + "y": 94 + }, + "basketball-11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 320, + "y": 187 + }, + "basketball-15": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 188, + "y": 94 + }, + "beer-11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 337, + "y": 187 + }, + "beer-15": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 209, + "y": 94 + }, + "bicycle-11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 354, + "y": 187 + }, + "bicycle-15": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 230, + "y": 94 + }, + "bicycle_rental-11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 371, + "y": 187 + }, + "bicycle_rental-15": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 124, + "y": 0 + }, + "building-11": { + "height": 15, + "pixelRatio": 1, + "width": 15, + "x": 359, + "y": 0 + }, + "building-15": { + "height": 19, + "pixelRatio": 1, + "width": 19, + "x": 179, + "y": 229 + }, + "bus-11": { + "height": 15, + "pixelRatio": 1, + "width": 15, + "x": 374, + "y": 0 + }, + "bus-15": { + "height": 19, + "pixelRatio": 1, + "width": 19, + "x": 198, + "y": 229 + }, + "butcher-11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 388, + "y": 187 + }, + "butcher-15": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 145, + "y": 0 + }, + "ca-transcanada_2": { + "height": 30, + "pixelRatio": 1, + "width": 30, + "x": 64, + "y": 0 + }, + "cafe-11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 405, + "y": 187 + }, + "cafe-15": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 166, + "y": 0 + }, + "campsite-11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 422, + "y": 187 + }, + "campsite-15": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 187, + "y": 0 + }, + "car-11": { + "height": 15, + "pixelRatio": 1, + "width": 15, + "x": 389, + "y": 0 + }, + "car-15": { + "height": 19, + "pixelRatio": 1, + "width": 19, + "x": 217, + "y": 229 + }, + "castle-11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 439, + "y": 187 + }, + "castle-15": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 208, + "y": 0 + }, + "cemetery-11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 456, + "y": 187 + }, + "cemetery-15": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 229, + "y": 0 + }, + "cinema-11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 473, + "y": 187 + }, + "cinema-15": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 0, + "y": 124 + }, + "circle-stroked-11": { + "height": 15, + "pixelRatio": 1, + "width": 15, + "x": 404, + "y": 0 + }, + "circle-stroked-15": { + "height": 19, + "pixelRatio": 1, + "width": 19, + "x": 236, + "y": 229 + }, + "circle-11": { + "height": 15, + "pixelRatio": 1, + "width": 15, + "x": 419, + "y": 0 + }, + "circle-15": { + "height": 19, + "pixelRatio": 1, + "width": 19, + "x": 252, + "y": 124 + }, + "clothing_store-11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 490, + "y": 187 + }, + "clothing_store-15": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 21, + "y": 124 + }, + "college-11": { + "height": 16, + "pixelRatio": 1, + "width": 16, + "x": 250, + "y": 0 + }, + "college-15": { + "height": 20, + "pixelRatio": 1, + "width": 20, + "x": 42, + "y": 229 + }, + "commercial-11": { + "height": 15, + "pixelRatio": 1, + "width": 15, + "x": 434, + "y": 0 + }, + "commercial-15": { + "height": 19, + "pixelRatio": 1, + "width": 19, + "x": 271, + "y": 124 + }, + "cricket-11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 252, + "y": 208 + }, + "cricket-15": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 42, + "y": 124 + }, + "cross-11": { + "height": 15, + "pixelRatio": 1, + "width": 15, + "x": 449, + "y": 0 + }, + "cross-15": { + "height": 19, + "pixelRatio": 1, + "width": 19, + "x": 290, + "y": 124 + }, + "dam-11": { + "height": 15, + "pixelRatio": 1, + "width": 15, + "x": 464, + "y": 0 + }, + "dam-15": { + "height": 19, + "pixelRatio": 1, + "width": 19, + "x": 309, + "y": 124 + }, + "danger-11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 269, + "y": 208 + }, + "danger-15": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 63, + "y": 124 + }, + "default_1": { + "height": 18, + "pixelRatio": 1, + "width": 18, + "x": 442, + "y": 145 + }, + "default_2": { + "height": 18, + "pixelRatio": 1, + "width": 25, + "x": 460, + "y": 145 + }, + "default_3": { + "height": 18, + "pixelRatio": 1, + "width": 32, + "x": 252, + "y": 166 + }, + "default_4": { + "height": 18, + "pixelRatio": 1, + "width": 39, + "x": 284, + "y": 166 + }, + "default_5": { + "height": 18, + "pixelRatio": 1, + "width": 45, + "x": 323, + "y": 166 + }, + "default_6": { + "height": 18, + "pixelRatio": 1, + "width": 50, + "x": 368, + "y": 166 + }, + "dentist-11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 286, + "y": 208 + }, + "dentist-15": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 84, + "y": 124 + }, + "doctor-11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 303, + "y": 208 + }, + "doctor-15": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 105, + "y": 124 + }, + "dog_park-11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 320, + "y": 208 + }, + "dog_park-15": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 126, + "y": 124 + }, + "dot-10": { + "height": 10, + "pixelRatio": 1, + "width": 10, + "x": 266, + "y": 250 + }, + "dot-11": { + "height": 11, + "pixelRatio": 1, + "width": 11, + "x": 255, + "y": 250 + }, + "dot_9": { + "height": 9, + "pixelRatio": 1, + "width": 9, + "x": 276, + "y": 250 + }, + "drinking-water-11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 337, + "y": 208 + }, + "drinking_water-15": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 147, + "y": 124 + }, + "embassy-11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 354, + "y": 208 + }, + "embassy-15": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 168, + "y": 124 + }, + "entrance-11": { + "height": 15, + "pixelRatio": 1, + "width": 15, + "x": 479, + "y": 0 + }, + "entrance-15": { + "height": 19, + "pixelRatio": 1, + "width": 19, + "x": 328, + "y": 124 + }, + "fast_food-11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 371, + "y": 208 + }, + "fast_food-15": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 189, + "y": 124 + }, + "ferry-11": { + "height": 15, + "pixelRatio": 1, + "width": 15, + "x": 494, + "y": 0 + }, + "ferry-15": { + "height": 19, + "pixelRatio": 1, + "width": 19, + "x": 347, + "y": 124 + }, + "fire-station-11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 388, + "y": 208 + }, + "fire-station-15": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 210, + "y": 124 + }, + "fuel-11": { + "height": 15, + "pixelRatio": 1, + "width": 15, + "x": 0, + "y": 250 + }, + "fuel-15": { + "height": 19, + "pixelRatio": 1, + "width": 19, + "x": 366, + "y": 124 + }, + "garden-11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 405, + "y": 208 + }, + "garden-15": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 231, + "y": 124 + }, + "gb-motorway_3": { + "height": 30, + "pixelRatio": 1, + "width": 50, + "x": 0, + "y": 64 + }, + "gift-11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 422, + "y": 208 + }, + "gift-15": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 0, + "y": 145 + }, + "golf-11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 439, + "y": 208 + }, + "golf-15": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 21, + "y": 145 + }, + "grocery-11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 456, + "y": 208 + }, + "grocery-15": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 42, + "y": 145 + }, + "hairdresser-11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 473, + "y": 208 + }, + "hairdresser-15": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 63, + "y": 145 + }, + "harbor-11": { + "height": 15, + "pixelRatio": 1, + "width": 15, + "x": 15, + "y": 250 + }, + "harbor-15": { + "height": 19, + "pixelRatio": 1, + "width": 19, + "x": 385, + "y": 124 + }, + "heart-11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 490, + "y": 208 + }, + "heart-15": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 84, + "y": 145 + }, + "heliport-11": { + "height": 15, + "pixelRatio": 1, + "width": 15, + "x": 30, + "y": 250 + }, + "heliport-15": { + "height": 19, + "pixelRatio": 1, + "width": 19, + "x": 404, + "y": 124 + }, + "hospital-11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 255, + "y": 229 + }, + "hospital-15": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 105, + "y": 145 + }, + "ice_cream-11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 272, + "y": 229 + }, + "ice_cream-15": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 126, + "y": 145 + }, + "industry-11": { + "height": 15, + "pixelRatio": 1, + "width": 15, + "x": 45, + "y": 250 + }, + "industry-15": { + "height": 19, + "pixelRatio": 1, + "width": 19, + "x": 423, + "y": 124 + }, + "information-11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 289, + "y": 229 + }, + "information-15": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 147, + "y": 145 + }, + "laundry-11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 306, + "y": 229 + }, + "laundry-15": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 168, + "y": 145 + }, + "library-11": { + "height": 16, + "pixelRatio": 1, + "width": 16, + "x": 266, + "y": 0 + }, + "library-15": { + "height": 20, + "pixelRatio": 1, + "width": 20, + "x": 62, + "y": 229 + }, + "lighthouse-11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 323, + "y": 229 + }, + "lighthouse-15": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 189, + "y": 145 + }, + "lodging-11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 340, + "y": 229 + }, + "lodging-15": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 210, + "y": 145 + }, + "marker-11": { + "height": 15, + "pixelRatio": 1, + "width": 15, + "x": 60, + "y": 250 + }, + "marker-15": { + "height": 19, + "pixelRatio": 1, + "width": 19, + "x": 442, + "y": 124 + }, + "monument-11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 357, + "y": 229 + }, + "monument-15": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 231, + "y": 145 + }, + "mountain-11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 374, + "y": 229 + }, + "mountain-15": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 0, + "y": 166 + }, + "museum-11": { + "height": 16, + "pixelRatio": 1, + "width": 16, + "x": 282, + "y": 0 + }, + "museum-15": { + "height": 20, + "pixelRatio": 1, + "width": 20, + "x": 82, + "y": 229 + }, + "music-11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 391, + "y": 229 + }, + "music-15": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 21, + "y": 166 + }, + "park-11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 408, + "y": 229 + }, + "park-15": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 42, + "y": 166 + }, + "parking-11": { + "height": 15, + "pixelRatio": 1, + "width": 15, + "x": 75, + "y": 250 + }, + "parking-15": { + "height": 19, + "pixelRatio": 1, + "width": 19, + "x": 461, + "y": 124 + }, + "parking_garage-11": { + "height": 15, + "pixelRatio": 1, + "width": 15, + "x": 90, + "y": 250 + }, + "parking_garage-15": { + "height": 19, + "pixelRatio": 1, + "width": 19, + "x": 480, + "y": 124 + }, + "pedestrian_polygon": { + "height": 64, + "pixelRatio": 1, + "width": 64, + "x": 0, + "y": 0 + }, + "pharmacy-11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 425, + "y": 229 + }, + "pharmacy-15": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 63, + "y": 166 + }, + "picnic_site-11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 442, + "y": 229 + }, + "picnic_site-15": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 84, + "y": 166 + }, + "pitch-11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 459, + "y": 229 + }, + "pitch-15": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 105, + "y": 166 + }, + "place_of_worship-11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 476, + "y": 229 + }, + "place_of_worship-15": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 126, + "y": 166 + }, + "playground-11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 493, + "y": 229 + }, + "playground-15": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 147, + "y": 166 + }, + "police-11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 243, + "y": 64 + }, + "police-15": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 168, + "y": 166 + }, + "post-11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 260, + "y": 64 + }, + "post-15": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 189, + "y": 166 + }, + "prison-11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 277, + "y": 64 + }, + "prison-15": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 210, + "y": 166 + }, + "railway-11": { + "height": 15, + "pixelRatio": 1, + "width": 15, + "x": 105, + "y": 250 + }, + "railway-15": { + "height": 19, + "pixelRatio": 1, + "width": 19, + "x": 252, + "y": 145 + }, + "railway_light-11": { + "height": 15, + "pixelRatio": 1, + "width": 15, + "x": 120, + "y": 250 + }, + "railway_light-15": { + "height": 19, + "pixelRatio": 1, + "width": 19, + "x": 271, + "y": 145 + }, + "railway_metro-11": { + "height": 15, + "pixelRatio": 1, + "width": 15, + "x": 135, + "y": 250 + }, + "railway_metro-15": { + "height": 19, + "pixelRatio": 1, + "width": 19, + "x": 290, + "y": 145 + }, + "ranger_station-11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 294, + "y": 64 + }, + "ranger_station-15": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 231, + "y": 166 + }, + "religious_christian-11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 311, + "y": 64 + }, + "religious_christian-15": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 0, + "y": 187 + }, + "religious_jewish-11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 328, + "y": 64 + }, + "religious_jewish-15": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 21, + "y": 187 + }, + "religious_muslim-11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 345, + "y": 64 + }, + "religious_muslim-15": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 42, + "y": 187 + }, + "restaurant-11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 362, + "y": 64 + }, + "restaurant-15": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 63, + "y": 187 + }, + "roadblock-11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 379, + "y": 64 + }, + "roadblock-15": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 84, + "y": 187 + }, + "rocket-11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 396, + "y": 64 + }, + "rocket-15": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 105, + "y": 187 + }, + "school-11": { + "height": 16, + "pixelRatio": 1, + "width": 16, + "x": 298, + "y": 0 + }, + "school-15": { + "height": 20, + "pixelRatio": 1, + "width": 20, + "x": 102, + "y": 229 + }, + "shelter-11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 413, + "y": 64 + }, + "shelter-15": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 126, + "y": 187 + }, + "shop-11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 430, + "y": 64 + }, + "shop-15": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 147, + "y": 187 + }, + "skiing-11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 447, + "y": 64 + }, + "skiing-15": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 168, + "y": 187 + }, + "soccer-11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 464, + "y": 64 + }, + "soccer-15": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 189, + "y": 187 + }, + "square-stroke-11": { + "height": 15, + "pixelRatio": 1, + "width": 15, + "x": 150, + "y": 250 + }, + "square-stroke-15": { + "height": 19, + "pixelRatio": 1, + "width": 19, + "x": 309, + "y": 145 + }, + "square-11": { + "height": 15, + "pixelRatio": 1, + "width": 15, + "x": 165, + "y": 250 + }, + "square-15": { + "height": 19, + "pixelRatio": 1, + "width": 19, + "x": 328, + "y": 145 + }, + "stadium-11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 481, + "y": 64 + }, + "stadium-15": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 210, + "y": 187 + }, + "star-stroke-11": { + "height": 15, + "pixelRatio": 1, + "width": 15, + "x": 180, + "y": 250 + }, + "star-stroke-15": { + "height": 19, + "pixelRatio": 1, + "width": 19, + "x": 347, + "y": 145 + }, + "star-11": { + "height": 15, + "pixelRatio": 1, + "width": 15, + "x": 195, + "y": 250 + }, + "star-15": { + "height": 19, + "pixelRatio": 1, + "width": 19, + "x": 366, + "y": 145 + }, + "suitcase-11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 251, + "y": 94 + }, + "suitcase-15": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 231, + "y": 187 + }, + "sushi-11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 268, + "y": 94 + }, + "sushi-15": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 0, + "y": 208 + }, + "swimming-11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 285, + "y": 94 + }, + "swimming-15": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 21, + "y": 208 + }, + "telephone-11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 302, + "y": 94 + }, + "telephone-15": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 42, + "y": 208 + }, + "tennis-11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 319, + "y": 94 + }, + "tennis-15": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 63, + "y": 208 + }, + "theatre-11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 336, + "y": 94 + }, + "theatre-15": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 84, + "y": 208 + }, + "toilet-11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 353, + "y": 94 + }, + "toilet-15": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 105, + "y": 208 + }, + "town_hall-11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 370, + "y": 94 + }, + "town_hall-15": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 126, + "y": 208 + }, + "triangle-11": { + "height": 15, + "pixelRatio": 1, + "width": 15, + "x": 210, + "y": 250 + }, + "triangle-15": { + "height": 19, + "pixelRatio": 1, + "width": 19, + "x": 385, + "y": 145 + }, + "triangle_stroked-11": { + "height": 15, + "pixelRatio": 1, + "width": 15, + "x": 225, + "y": 250 + }, + "triangle_stroked-15": { + "height": 19, + "pixelRatio": 1, + "width": 19, + "x": 404, + "y": 145 + }, + "us-highway_2": { + "height": 30, + "pixelRatio": 1, + "width": 30, + "x": 50, + "y": 64 + }, + "us-highway_3": { + "height": 30, + "pixelRatio": 1, + "width": 37, + "x": 80, + "y": 64 + }, + "us-interstate_2": { + "height": 30, + "pixelRatio": 1, + "width": 30, + "x": 94, + "y": 0 + }, + "us-interstate_3": { + "height": 30, + "pixelRatio": 1, + "width": 37, + "x": 0, + "y": 94 + }, + "us-state_2": { + "height": 30, + "pixelRatio": 1, + "width": 30, + "x": 37, + "y": 94 + }, + "us-state_3": { + "height": 30, + "pixelRatio": 1, + "width": 37, + "x": 67, + "y": 94 + }, + "veterinary-11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 387, + "y": 94 + }, + "veterinary-15": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 147, + "y": 208 + }, + "volcano-11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 404, + "y": 94 + }, + "volcano-15": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 168, + "y": 208 + }, + "warehouse-11": { + "height": 15, + "pixelRatio": 1, + "width": 15, + "x": 240, + "y": 250 + }, + "warehouse-15": { + "height": 19, + "pixelRatio": 1, + "width": 19, + "x": 423, + "y": 145 + }, + "waste_basket-11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 421, + "y": 94 + }, + "waste_basket-15": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 189, + "y": 208 + }, + "water-11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 438, + "y": 94 + }, + "water-15": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 210, + "y": 208 + }, + "wetland-11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 455, + "y": 94 + }, + "wetland-15": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 231, + "y": 208 + }, + "wheelchair-11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 472, + "y": 94 + }, + "wheelchair-15": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 0, + "y": 229 + }, + "zoo-11": { + "height": 17, + "pixelRatio": 1, + "width": 17, + "x": 489, + "y": 94 + }, + "zoo-15": { + "height": 21, + "pixelRatio": 1, + "width": 21, + "x": 21, + "y": 229 + } +} \ No newline at end of file diff --git a/web/assets/sprites/osm-liberty.png b/web/assets/sprites/osm-liberty.png new file mode 100644 index 0000000..61f15ac Binary files /dev/null and b/web/assets/sprites/osm-liberty.png differ diff --git a/web/assets/sprites/osm-liberty@2x.json b/web/assets/sprites/osm-liberty@2x.json new file mode 100644 index 0000000..649cea8 --- /dev/null +++ b/web/assets/sprites/osm-liberty@2x.json @@ -0,0 +1,1745 @@ +{ + "aerialway-11": { + "height": 30, + "pixelRatio": 2, + "width": 30, + "x": 628, + "y": 0 + }, + "aerialway-15": { + "height": 38, + "pixelRatio": 2, + "width": 38, + "x": 244, + "y": 458 + }, + "airfield-11": { + "height": 30, + "pixelRatio": 2, + "width": 30, + "x": 658, + "y": 0 + }, + "airfield-15": { + "height": 38, + "pixelRatio": 2, + "width": 38, + "x": 282, + "y": 458 + }, + "airport-11": { + "height": 30, + "pixelRatio": 2, + "width": 30, + "x": 688, + "y": 0 + }, + "airport-15": { + "height": 38, + "pixelRatio": 2, + "width": 38, + "x": 320, + "y": 458 + }, + "alcohol_shop-11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 970, + "y": 290 + }, + "alcohol_shop-15": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 210, + "y": 188 + }, + "america_football-11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 836, + "y": 332 + }, + "america_football-15": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 235, + "y": 128 + }, + "amusement_park-11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 870, + "y": 332 + }, + "amusement_park-15": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 277, + "y": 128 + }, + "aquarium-11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 904, + "y": 332 + }, + "aquarium-15": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 319, + "y": 128 + }, + "arrow": { + "height": 14, + "pixelRatio": 2, + "width": 40, + "x": 570, + "y": 500 + }, + "art_gallery-11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 938, + "y": 332 + }, + "art_gallery-15": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 361, + "y": 128 + }, + "attraction-11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 972, + "y": 332 + }, + "attraction-15": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 403, + "y": 128 + }, + "bakery-11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 504, + "y": 374 + }, + "bakery-15": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 445, + "y": 128 + }, + "bank-11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 538, + "y": 374 + }, + "bank-15": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 252, + "y": 188 + }, + "bar-11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 572, + "y": 374 + }, + "bar-15": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 294, + "y": 188 + }, + "baseball-11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 606, + "y": 374 + }, + "baseball-15": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 336, + "y": 188 + }, + "basketball-11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 640, + "y": 374 + }, + "basketball-15": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 378, + "y": 188 + }, + "beer-11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 674, + "y": 374 + }, + "beer-15": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 420, + "y": 188 + }, + "bicycle-11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 708, + "y": 374 + }, + "bicycle-15": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 462, + "y": 188 + }, + "bicycle_rental-11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 742, + "y": 374 + }, + "bicycle_rental-15": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 248, + "y": 0 + }, + "building-11": { + "height": 30, + "pixelRatio": 2, + "width": 30, + "x": 718, + "y": 0 + }, + "building-15": { + "height": 38, + "pixelRatio": 2, + "width": 38, + "x": 358, + "y": 458 + }, + "bus-11": { + "height": 30, + "pixelRatio": 2, + "width": 30, + "x": 748, + "y": 0 + }, + "bus-15": { + "height": 38, + "pixelRatio": 2, + "width": 38, + "x": 396, + "y": 458 + }, + "butcher-11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 776, + "y": 374 + }, + "butcher-15": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 290, + "y": 0 + }, + "ca-transcanada_2": { + "height": 60, + "pixelRatio": 2, + "width": 60, + "x": 128, + "y": 0 + }, + "cafe-11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 810, + "y": 374 + }, + "cafe-15": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 332, + "y": 0 + }, + "campsite-11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 844, + "y": 374 + }, + "campsite-15": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 374, + "y": 0 + }, + "car-11": { + "height": 30, + "pixelRatio": 2, + "width": 30, + "x": 778, + "y": 0 + }, + "car-15": { + "height": 38, + "pixelRatio": 2, + "width": 38, + "x": 434, + "y": 458 + }, + "castle-11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 878, + "y": 374 + }, + "castle-15": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 416, + "y": 0 + }, + "cemetery-11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 912, + "y": 374 + }, + "cemetery-15": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 458, + "y": 0 + }, + "cinema-11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 946, + "y": 374 + }, + "cinema-15": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 0, + "y": 248 + }, + "circle-stroked-11": { + "height": 30, + "pixelRatio": 2, + "width": 30, + "x": 808, + "y": 0 + }, + "circle-stroked-15": { + "height": 38, + "pixelRatio": 2, + "width": 38, + "x": 472, + "y": 458 + }, + "circle-11": { + "height": 30, + "pixelRatio": 2, + "width": 30, + "x": 838, + "y": 0 + }, + "circle-15": { + "height": 38, + "pixelRatio": 2, + "width": 38, + "x": 504, + "y": 248 + }, + "clothing_store-11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 980, + "y": 374 + }, + "clothing_store-15": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 42, + "y": 248 + }, + "college-11": { + "height": 32, + "pixelRatio": 2, + "width": 32, + "x": 500, + "y": 0 + }, + "college-15": { + "height": 40, + "pixelRatio": 2, + "width": 40, + "x": 84, + "y": 458 + }, + "commercial-11": { + "height": 30, + "pixelRatio": 2, + "width": 30, + "x": 868, + "y": 0 + }, + "commercial-15": { + "height": 38, + "pixelRatio": 2, + "width": 38, + "x": 542, + "y": 248 + }, + "cricket-11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 504, + "y": 416 + }, + "cricket-15": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 84, + "y": 248 + }, + "cross-11": { + "height": 30, + "pixelRatio": 2, + "width": 30, + "x": 898, + "y": 0 + }, + "cross-15": { + "height": 38, + "pixelRatio": 2, + "width": 38, + "x": 580, + "y": 248 + }, + "dam-11": { + "height": 30, + "pixelRatio": 2, + "width": 30, + "x": 928, + "y": 0 + }, + "dam-15": { + "height": 38, + "pixelRatio": 2, + "width": 38, + "x": 618, + "y": 248 + }, + "danger-11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 538, + "y": 416 + }, + "danger-15": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 126, + "y": 248 + }, + "default_1": { + "height": 36, + "pixelRatio": 2, + "width": 36, + "x": 884, + "y": 290 + }, + "default_2": { + "height": 36, + "pixelRatio": 2, + "width": 50, + "x": 920, + "y": 290 + }, + "default_3": { + "height": 36, + "pixelRatio": 2, + "width": 64, + "x": 504, + "y": 332 + }, + "default_4": { + "height": 36, + "pixelRatio": 2, + "width": 78, + "x": 568, + "y": 332 + }, + "default_5": { + "height": 36, + "pixelRatio": 2, + "width": 90, + "x": 646, + "y": 332 + }, + "default_6": { + "height": 36, + "pixelRatio": 2, + "width": 100, + "x": 736, + "y": 332 + }, + "dentist-11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 572, + "y": 416 + }, + "dentist-15": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 168, + "y": 248 + }, + "doctor-11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 606, + "y": 416 + }, + "doctor-15": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 210, + "y": 248 + }, + "dog_park-11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 640, + "y": 416 + }, + "dog_park-15": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 252, + "y": 248 + }, + "dot-10": { + "height": 20, + "pixelRatio": 2, + "width": 20, + "x": 532, + "y": 500 + }, + "dot-11": { + "height": 22, + "pixelRatio": 2, + "width": 22, + "x": 510, + "y": 500 + }, + "dot_9": { + "height": 18, + "pixelRatio": 2, + "width": 18, + "x": 552, + "y": 500 + }, + "drinking-water-11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 674, + "y": 416 + }, + "drinking_water-15": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 294, + "y": 248 + }, + "embassy-11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 708, + "y": 416 + }, + "embassy-15": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 336, + "y": 248 + }, + "entrance-11": { + "height": 30, + "pixelRatio": 2, + "width": 30, + "x": 958, + "y": 0 + }, + "entrance-15": { + "height": 38, + "pixelRatio": 2, + "width": 38, + "x": 656, + "y": 248 + }, + "fast_food-11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 742, + "y": 416 + }, + "fast_food-15": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 378, + "y": 248 + }, + "ferry-11": { + "height": 30, + "pixelRatio": 2, + "width": 30, + "x": 988, + "y": 0 + }, + "ferry-15": { + "height": 38, + "pixelRatio": 2, + "width": 38, + "x": 694, + "y": 248 + }, + "fire-station-11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 776, + "y": 416 + }, + "fire-station-15": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 420, + "y": 248 + }, + "fuel-11": { + "height": 30, + "pixelRatio": 2, + "width": 30, + "x": 0, + "y": 500 + }, + "fuel-15": { + "height": 38, + "pixelRatio": 2, + "width": 38, + "x": 732, + "y": 248 + }, + "garden-11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 810, + "y": 416 + }, + "garden-15": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 462, + "y": 248 + }, + "gb-motorway_3": { + "height": 60, + "pixelRatio": 2, + "width": 100, + "x": 0, + "y": 128 + }, + "gift-11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 844, + "y": 416 + }, + "gift-15": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 0, + "y": 290 + }, + "golf-11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 878, + "y": 416 + }, + "golf-15": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 42, + "y": 290 + }, + "grocery-11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 912, + "y": 416 + }, + "grocery-15": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 84, + "y": 290 + }, + "hairdresser-11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 946, + "y": 416 + }, + "hairdresser-15": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 126, + "y": 290 + }, + "harbor-11": { + "height": 30, + "pixelRatio": 2, + "width": 30, + "x": 30, + "y": 500 + }, + "harbor-15": { + "height": 38, + "pixelRatio": 2, + "width": 38, + "x": 770, + "y": 248 + }, + "heart-11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 980, + "y": 416 + }, + "heart-15": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 168, + "y": 290 + }, + "heliport-11": { + "height": 30, + "pixelRatio": 2, + "width": 30, + "x": 60, + "y": 500 + }, + "heliport-15": { + "height": 38, + "pixelRatio": 2, + "width": 38, + "x": 808, + "y": 248 + }, + "hospital-11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 510, + "y": 458 + }, + "hospital-15": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 210, + "y": 290 + }, + "ice_cream-11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 544, + "y": 458 + }, + "ice_cream-15": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 252, + "y": 290 + }, + "industry-11": { + "height": 30, + "pixelRatio": 2, + "width": 30, + "x": 90, + "y": 500 + }, + "industry-15": { + "height": 38, + "pixelRatio": 2, + "width": 38, + "x": 846, + "y": 248 + }, + "information-11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 578, + "y": 458 + }, + "information-15": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 294, + "y": 290 + }, + "laundry-11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 612, + "y": 458 + }, + "laundry-15": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 336, + "y": 290 + }, + "library-11": { + "height": 32, + "pixelRatio": 2, + "width": 32, + "x": 532, + "y": 0 + }, + "library-15": { + "height": 40, + "pixelRatio": 2, + "width": 40, + "x": 124, + "y": 458 + }, + "lighthouse-11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 646, + "y": 458 + }, + "lighthouse-15": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 378, + "y": 290 + }, + "lodging-11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 680, + "y": 458 + }, + "lodging-15": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 420, + "y": 290 + }, + "marker-11": { + "height": 30, + "pixelRatio": 2, + "width": 30, + "x": 120, + "y": 500 + }, + "marker-15": { + "height": 38, + "pixelRatio": 2, + "width": 38, + "x": 884, + "y": 248 + }, + "monument-11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 714, + "y": 458 + }, + "monument-15": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 462, + "y": 290 + }, + "mountain-11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 748, + "y": 458 + }, + "mountain-15": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 0, + "y": 332 + }, + "museum-11": { + "height": 32, + "pixelRatio": 2, + "width": 32, + "x": 564, + "y": 0 + }, + "museum-15": { + "height": 40, + "pixelRatio": 2, + "width": 40, + "x": 164, + "y": 458 + }, + "music-11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 782, + "y": 458 + }, + "music-15": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 42, + "y": 332 + }, + "park-11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 816, + "y": 458 + }, + "park-15": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 84, + "y": 332 + }, + "parking-11": { + "height": 30, + "pixelRatio": 2, + "width": 30, + "x": 150, + "y": 500 + }, + "parking-15": { + "height": 38, + "pixelRatio": 2, + "width": 38, + "x": 922, + "y": 248 + }, + "parking_garage-11": { + "height": 30, + "pixelRatio": 2, + "width": 30, + "x": 180, + "y": 500 + }, + "parking_garage-15": { + "height": 38, + "pixelRatio": 2, + "width": 38, + "x": 960, + "y": 248 + }, + "pedestrian_polygon": { + "height": 128, + "pixelRatio": 2, + "width": 128, + "x": 0, + "y": 0 + }, + "pharmacy-11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 850, + "y": 458 + }, + "pharmacy-15": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 126, + "y": 332 + }, + "picnic_site-11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 884, + "y": 458 + }, + "picnic_site-15": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 168, + "y": 332 + }, + "pitch-11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 918, + "y": 458 + }, + "pitch-15": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 210, + "y": 332 + }, + "place_of_worship-11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 952, + "y": 458 + }, + "place_of_worship-15": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 252, + "y": 332 + }, + "playground-11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 986, + "y": 458 + }, + "playground-15": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 294, + "y": 332 + }, + "police-11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 487, + "y": 128 + }, + "police-15": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 336, + "y": 332 + }, + "post-11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 521, + "y": 128 + }, + "post-15": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 378, + "y": 332 + }, + "prison-11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 555, + "y": 128 + }, + "prison-15": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 420, + "y": 332 + }, + "railway-11": { + "height": 30, + "pixelRatio": 2, + "width": 30, + "x": 210, + "y": 500 + }, + "railway-15": { + "height": 38, + "pixelRatio": 2, + "width": 38, + "x": 504, + "y": 290 + }, + "railway_light-11": { + "height": 30, + "pixelRatio": 2, + "width": 30, + "x": 240, + "y": 500 + }, + "railway_light-15": { + "height": 38, + "pixelRatio": 2, + "width": 38, + "x": 542, + "y": 290 + }, + "railway_metro-11": { + "height": 30, + "pixelRatio": 2, + "width": 30, + "x": 270, + "y": 500 + }, + "railway_metro-15": { + "height": 38, + "pixelRatio": 2, + "width": 38, + "x": 580, + "y": 290 + }, + "ranger_station-11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 589, + "y": 128 + }, + "ranger_station-15": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 462, + "y": 332 + }, + "religious_christian-11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 623, + "y": 128 + }, + "religious_christian-15": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 0, + "y": 374 + }, + "religious_jewish-11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 657, + "y": 128 + }, + "religious_jewish-15": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 42, + "y": 374 + }, + "religious_muslim-11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 691, + "y": 128 + }, + "religious_muslim-15": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 84, + "y": 374 + }, + "restaurant-11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 725, + "y": 128 + }, + "restaurant-15": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 126, + "y": 374 + }, + "roadblock-11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 759, + "y": 128 + }, + "roadblock-15": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 168, + "y": 374 + }, + "rocket-11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 793, + "y": 128 + }, + "rocket-15": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 210, + "y": 374 + }, + "school-11": { + "height": 32, + "pixelRatio": 2, + "width": 32, + "x": 596, + "y": 0 + }, + "school-15": { + "height": 40, + "pixelRatio": 2, + "width": 40, + "x": 204, + "y": 458 + }, + "shelter-11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 827, + "y": 128 + }, + "shelter-15": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 252, + "y": 374 + }, + "shop-11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 861, + "y": 128 + }, + "shop-15": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 294, + "y": 374 + }, + "skiing-11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 895, + "y": 128 + }, + "skiing-15": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 336, + "y": 374 + }, + "soccer-11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 929, + "y": 128 + }, + "soccer-15": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 378, + "y": 374 + }, + "square-stroke-11": { + "height": 30, + "pixelRatio": 2, + "width": 30, + "x": 300, + "y": 500 + }, + "square-stroke-15": { + "height": 38, + "pixelRatio": 2, + "width": 38, + "x": 618, + "y": 290 + }, + "square-11": { + "height": 30, + "pixelRatio": 2, + "width": 30, + "x": 330, + "y": 500 + }, + "square-15": { + "height": 38, + "pixelRatio": 2, + "width": 38, + "x": 656, + "y": 290 + }, + "stadium-11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 963, + "y": 128 + }, + "stadium-15": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 420, + "y": 374 + }, + "star-stroke-11": { + "height": 30, + "pixelRatio": 2, + "width": 30, + "x": 360, + "y": 500 + }, + "star-stroke-15": { + "height": 38, + "pixelRatio": 2, + "width": 38, + "x": 694, + "y": 290 + }, + "star-11": { + "height": 30, + "pixelRatio": 2, + "width": 30, + "x": 390, + "y": 500 + }, + "star-15": { + "height": 38, + "pixelRatio": 2, + "width": 38, + "x": 732, + "y": 290 + }, + "suitcase-11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 504, + "y": 188 + }, + "suitcase-15": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 462, + "y": 374 + }, + "sushi-11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 538, + "y": 188 + }, + "sushi-15": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 0, + "y": 416 + }, + "swimming-11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 572, + "y": 188 + }, + "swimming-15": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 42, + "y": 416 + }, + "telephone-11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 606, + "y": 188 + }, + "telephone-15": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 84, + "y": 416 + }, + "tennis-11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 640, + "y": 188 + }, + "tennis-15": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 126, + "y": 416 + }, + "theatre-11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 674, + "y": 188 + }, + "theatre-15": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 168, + "y": 416 + }, + "toilet-11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 708, + "y": 188 + }, + "toilet-15": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 210, + "y": 416 + }, + "town_hall-11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 742, + "y": 188 + }, + "town_hall-15": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 252, + "y": 416 + }, + "triangle-11": { + "height": 30, + "pixelRatio": 2, + "width": 30, + "x": 420, + "y": 500 + }, + "triangle-15": { + "height": 38, + "pixelRatio": 2, + "width": 38, + "x": 770, + "y": 290 + }, + "triangle_stroked-11": { + "height": 30, + "pixelRatio": 2, + "width": 30, + "x": 450, + "y": 500 + }, + "triangle_stroked-15": { + "height": 38, + "pixelRatio": 2, + "width": 38, + "x": 808, + "y": 290 + }, + "us-highway_2": { + "height": 60, + "pixelRatio": 2, + "width": 60, + "x": 100, + "y": 128 + }, + "us-highway_3": { + "height": 60, + "pixelRatio": 2, + "width": 75, + "x": 160, + "y": 128 + }, + "us-interstate_2": { + "height": 60, + "pixelRatio": 2, + "width": 60, + "x": 188, + "y": 0 + }, + "us-interstate_3": { + "height": 60, + "pixelRatio": 2, + "width": 75, + "x": 0, + "y": 188 + }, + "us-state_2": { + "height": 60, + "pixelRatio": 2, + "width": 60, + "x": 75, + "y": 188 + }, + "us-state_3": { + "height": 60, + "pixelRatio": 2, + "width": 75, + "x": 135, + "y": 188 + }, + "veterinary-11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 776, + "y": 188 + }, + "veterinary-15": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 294, + "y": 416 + }, + "volcano-11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 810, + "y": 188 + }, + "volcano-15": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 336, + "y": 416 + }, + "warehouse-11": { + "height": 30, + "pixelRatio": 2, + "width": 30, + "x": 480, + "y": 500 + }, + "warehouse-15": { + "height": 38, + "pixelRatio": 2, + "width": 38, + "x": 846, + "y": 290 + }, + "waste_basket-11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 844, + "y": 188 + }, + "waste_basket-15": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 378, + "y": 416 + }, + "water-11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 878, + "y": 188 + }, + "water-15": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 420, + "y": 416 + }, + "wetland-11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 912, + "y": 188 + }, + "wetland-15": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 462, + "y": 416 + }, + "wheelchair-11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 946, + "y": 188 + }, + "wheelchair-15": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 0, + "y": 458 + }, + "zoo-11": { + "height": 34, + "pixelRatio": 2, + "width": 34, + "x": 980, + "y": 188 + }, + "zoo-15": { + "height": 42, + "pixelRatio": 2, + "width": 42, + "x": 42, + "y": 458 + } +} \ No newline at end of file diff --git a/web/assets/sprites/osm-liberty@2x.png b/web/assets/sprites/osm-liberty@2x.png new file mode 100644 index 0000000..1b54e3e Binary files /dev/null and b/web/assets/sprites/osm-liberty@2x.png differ diff --git a/web/figure_filter.py b/web/figure_filter.py new file mode 100644 index 0000000..373d73c --- /dev/null +++ b/web/figure_filter.py @@ -0,0 +1,121 @@ +import json +from logging import DEBUG + +from dash.dependencies import Output, Input +from dash_core_components import Store +from mylogger import logger + + +class Graph: + def __init__(self, graph_id, x, y: [], figure): + self.graph_id = graph_id + self.x = x + self.y = y + self.figure = figure + + +class Table: + def __init__(self, table_id, src, figure): + self.table_id = table_id + self.src = src + self.figure = figure + self.date_columns = [] + + +def figures_to_dict(figures): + el_list = [] + for figure in figures: + res = {} + for key, value in figure.__dict__.items(): + if key != "figure": + res[key] = value + el_list.append(res) + return el_list + + +class Figure_Filter: + + def __init__(self): + self.graphs = [] + self.tables = [] + self.maps = [] + self.src = {} + + def add_map(self, dash_Graph, latitude, longitude, figure): + self.maps.append(Graph(dash_Graph.id, latitude, longitude, figure)) + return dash_Graph + + def add_graph(self, dash_Graph, x, y, figure): + self.graphs.append(Graph(dash_Graph.id, x, y, figure)) + return dash_Graph + + def add_table(self, src, figure): + table = Table(figure.id, src, figure) + table.date_columns = [col["id"][:-4] for col in figure.columns if col["type"] == "datetime" and + col["id"].endswith("_str")] + self.tables.append(table) + + def __get_table_date_column_id(self): + res = {table.src: table.date_columns for table in self.tables} + return res + + def __get_table_src(self): + return [table.src for table in self.tables] + + def __get_figures(self): + return {"graph": [graph.figure for graph in self.graphs], + "tables": [table.figure for table in self.tables], + "maps": [map.figure for map in self.maps]} + + def __get_output(self) -> list: + outputs = [Output(table.table_id, "data") for table in self.tables] + outputs.extend([Output(graph.graph_id, "figure") for graph in self.graphs]) + outputs.extend([Output(graph.graph_id, "figure") for graph in self.maps]) + return outputs + + def __get_graph_x_label(self, graphs): + return [graph.x for graph in graphs] + + def __get_graph_y_label(self, graphs): + return [graph.y for graph in graphs] + + def __get_table_input_sort_by(self): + inputs = [Input(table.table_id, 'sort_by') for table in self.tables] + return inputs + + def gen_unused_variable(self): + res = ", ".join([chr(i) for i in range(ord('a'), ord('a') + len(self.tables))]) + return res + + def get_params(self): + params = json.dumps({ + "date_columns": self.__get_table_date_column_id(), + "table_src": figures_to_dict(self.tables), + "graph_x_label": self.__get_graph_x_label(self.graphs), + "graph_y_label": self.__get_graph_y_label(self.graphs), + "map_x_label": self.__get_graph_x_label(self.maps), + "map_y_label": self.__get_graph_y_label(self.maps) + }, indent=4) + return params + + def get_clientside_callback(self): + if logger.isEnabledFor(DEBUG): + log_level = 10 + else: + log_level = 20 + fct_def = f"""function(data,range, figures, {self.gen_unused_variable()}) {{ + var params={self.get_params()}; + var log_level={log_level}; + return filter_and_sort(data,range, figures, params, log_level) + }}""" + res = [fct_def, + *self.__get_output(), + Input('clientside-data-store', 'data'), + Input('date-slider', 'value'), + Input('clientside-figure-store', 'data'), + *self.__get_table_input_sort_by()] + return res + + def get_store(self): + return [Store(id='clientside-figure-store', data=self.__get_figures()), + Store(id='clientside-data-store', data=self.src)] diff --git a/web/figures.py b/web/figures.py index de3e0a8..50df6e3 100644 --- a/web/figures.py +++ b/web/figures.py @@ -1,7 +1,4 @@ from copy import deepcopy -from statistics import mean - -from typing import List import dash_bootstrap_components as dbc import dash_table @@ -10,13 +7,11 @@ from dash_table.Format import Format, Scheme, Symbol from dateutil.relativedelta import relativedelta import plotly.express as px import plotly.graph_objects as go -from pandas import DataFrame -from pandas import options as pandas_options import dash_html_components as html from libs.car import Car from libs.elec_price import ElecPrice -from trip import Trips, Trip +from trip import Trip from web.db import Database @@ -57,21 +52,41 @@ trips_map = ERROR_DIV consumption_fig_by_speed = ERROR_DIV consumption_fig_by_temp = ERROR_DIV table_fig = ERROR_DIV -pandas_options.display.float_format = '${:.2f}'.format info = "" battery_info = ERROR_DIV battery_table = None consumption_df_dict = None -SUMMARY_CARDS = {"Average consumption": {"text": None, "src": "assets/images/consumption.svg"}, - "Average emission": {"text": None, "src": "assets/images/pollution.svg"}, - "Average charge speed": {"text": None, "src": "assets/images/battery-charge-line.svg"}, - "Electricity consumption": {"text": None, "src": "assets/images/electricity bill.svg"} + +def card_value_div(card_id, unit, value="-"): + return html.Div([html.Div(value, id=card_id, className="mr-2"), html.Div(unit)], + className="d-flex flex-row justify-content-center") + + +AVG_CHARGE_SPEED = "avg_chg_speed" +AVG_EMISSION_KM = "avg_emission_km" +AVG_EMISSION_KW = "avg_emission_kw" +ELEC_CONSUM_KW = "elec_consum_kw" +ELEC_CONSUM_PRICE = "elec_consum_price" +AVG_CONSUM_KW = "avg_consum_kw" +AVG_CONSUM_PRICE = "avg_consum_price" + +SUMMARY_CARDS = {"Average consumption": {"text": [card_value_div(AVG_CONSUM_KW, "kWh/100km"), + card_value_div(AVG_CONSUM_PRICE, f"{ElecPrice.currency}/100km")], + "src": "assets/images/consumption.svg"}, + "Average emission": {"text": [card_value_div(AVG_EMISSION_KM, " g/km"), + card_value_div(AVG_EMISSION_KW, "g/kWh")], + "src": "assets/images/pollution.svg"}, + "Average charge speed": {"text": [card_value_div(AVG_CHARGE_SPEED, " kW")], + "src": "assets/images/battery-charge-line.svg"}, + "Electricity consumption": {"text": [card_value_div(ELEC_CONSUM_KW, "kWh"), + card_value_div(ELEC_CONSUM_PRICE, ElecPrice.currency)], + "src": "assets/images/electricity bill.svg"} } # pylint: disable=too-many-locals -def get_figures(trips: Trips, charging: List[dict]): +def get_figures(car: Car): global consumption_fig, consumption_df, trips_map, consumption_fig_by_speed, table_fig, info, battery_info, \ battery_table, consumption_fig_by_temp, consumption_df_dict lats = [42, 41] @@ -85,12 +100,17 @@ def get_figures(trips: Trips, charging: List[dict]): showlegend=False, name="Last Position")) # table nb_format = Format(precision=2, scheme=Scheme.fixed, symbol=Symbol.yes) # pylint: disable=no-member + style_cell_conditional = [] + if car.is_electric(): + style_cell_conditional.append({'if': {'column_id': 'consumption_fuel_km', }, 'display': 'None', }) + if car.is_thermal(): + style_cell_conditional.append({'if': {'column_id': 'consumption_km', }, 'display': 'None', }) table_fig = dash_table.DataTable( id='trips-table', - sort_action='native', + sort_action='custom', sort_by=[{'column_id': 'id', 'direction': 'desc'}], columns=[{'id': 'id', 'name': '#', 'type': 'numeric'}, - {'id': 'start_at', 'name': 'start at', 'type': 'datetime'}, + {'id': 'start_at_str', 'name': 'start at', 'type': 'datetime'}, {'id': 'duration', 'name': 'duration', 'type': 'numeric', 'format': deepcopy(nb_format).symbol_suffix(" min").precision(0)}, {'id': 'speed_average', 'name': 'average speed', 'type': 'numeric', @@ -113,50 +133,31 @@ def get_figures(trips: Trips, charging: List[dict]): "text-decoration": "underline" } ], - data=trips.get_info(), + style_cell_conditional=style_cell_conditional, + data=[], page_size=50 ) # consumption_fig - consumption_df_dict = trips.get_long_trips() consumption_fig = px.histogram(x=[0], y=[1], title='Consumption of the car', histfunc="avg") consumption_fig.update_layout(yaxis_title="Consumption kWh/100Km", xaxis_title="date") - consumption_fig_by_speed = px.histogram(x=[0], y=[1], histfunc="avg", + consumption_fig_by_speed = px.histogram(data_frame=[{"start_at": 1, "speed_average": 2}], x="start_at", + y="speed_average", histfunc="avg", title="Consumption by speed") consumption_fig_by_speed.update_traces(xbins_size=15) consumption_fig_by_speed.update_layout(bargap=0.05) consumption_fig_by_speed.add_trace(go.Scatter(mode="markers", x=[0], y=[0], name="Trips")) consumption_fig_by_speed.update_layout(xaxis_title="average Speed km/h", yaxis_title="Consumption kWh/100Km") - kw_per_km = mean([t.consumption_km for t in trips]) - info = "Average consumption: {:.1f} kWh/100km".format(kw_per_km) - # charging - charging_data = DataFrame.from_records(charging) - co2_per_kw = __calculate_co2_per_kw(charging_data) - co2_per_km = co2_per_kw * kw_per_km / 100 - try: - charge_speed = 3600 * charging_data["kw"].mean() / \ - (charging_data["stop_at"] - charging_data["start_at"]).mean().total_seconds() - price_kw = (charging_data["price"] / charging_data["kw"]).mean() - total_elec = kw_per_km * trips.get_distance() / 100 - except (TypeError, KeyError, ZeroDivisionError): # when there is no data yet: - charge_speed = 0 - price_kw = 0 - total_elec = 0 - - SUMMARY_CARDS["Average charge speed"]["text"] = f"{charge_speed:.2f} kW" - SUMMARY_CARDS["Average emission"]["text"] = [html.P(f"{co2_per_km:.1f} g/km"), html.P(f"{co2_per_kw:.1f} g/kWh")] - SUMMARY_CARDS["Electricity consumption"]["text"] = [f"{total_elec:.0f} kWh", html.Br(), \ - f"{total_elec * price_kw:.0f} {ElecPrice.currency}"] - SUMMARY_CARDS["Average consumption"]["text"] = f"{kw_per_km:.1f} kWh/100km" + # battery_table battery_table = dash_table.DataTable( id='battery-table', - sort_action='native', - sort_by=[{'column_id': 'start_at', 'direction': 'desc'}], - columns=[{'id': 'start_at', 'name': 'start at', 'type': 'datetime'}, - {'id': 'stop_at', 'name': 'stop at', 'type': 'datetime'}, + sort_action='custom', + sort_by=[{'column_id': 'start_at_str', 'direction': 'desc'}], + columns=[{'id': 'start_at_str', 'name': 'start at', 'type': 'datetime'}, + {'id': 'stop_at_str', 'name': 'stop at', 'type': 'datetime'}, {'id': 'start_level', 'name': 'start level', 'type': 'numeric'}, {'id': 'end_level', 'name': 'end level', 'type': 'numeric'}, {'id': 'co2', 'name': 'CO2', 'type': 'numeric', @@ -166,7 +167,7 @@ def get_figures(trips: Trips, charging: List[dict]): {'id': 'price', 'name': 'price', 'type': 'numeric', 'format': deepcopy(nb_format).symbol_suffix(" " + ElecPrice.currency).precision(2), 'editable': True} ], - data=charging, + data=[], style_data_conditional=[ { 'if': {'column_id': ['start_level', "end_level"]}, @@ -175,26 +176,19 @@ def get_figures(trips: Trips, charging: List[dict]): }, { 'if': {'column_id': 'price'}, - 'backgroundColor': 'rgb(230, 246, 254)' + 'backgroundColor': '#ABE2FB' } ], ) - consumption_fig_by_temp = None - temp_value = False - for trip in trips: - if trip.get_temperature() is not None: - temp_value = True - break - if temp_value: - consumption_fig_by_temp = px.histogram(x=[0], y=[0], - histfunc="avg", title="Consumption by temperature") - consumption_fig_by_temp.update_traces(xbins_size=2) - consumption_fig_by_temp.update_layout(bargap=0.05) - consumption_fig_by_temp.add_trace( - go.Scatter(mode="markers", x=[0], - y=[0], name="Trips")) - consumption_fig_by_temp.update_layout(xaxis_title="average temperature in °C", - yaxis_title="Consumption kWh/100Km") + consumption_fig_by_temp = px.histogram(x=[0], y=[0], + histfunc="avg", title="Consumption by temperature") + consumption_fig_by_temp.update_traces(xbins_size=2) + consumption_fig_by_temp.update_layout(bargap=0.05) + consumption_fig_by_temp.add_trace( + go.Scatter(mode="markers", x=[0], + y=[0], name="Trips")) + consumption_fig_by_temp.update_layout(xaxis_title="average temperature in °C", + yaxis_title="Consumption kWh/100Km") return True diff --git a/web/views.py b/web/views.py index 4083d70..dc229bf 100644 --- a/web/views.py +++ b/web/views.py @@ -1,5 +1,4 @@ import json -from datetime import datetime, timezone from typing import List import dash_bootstrap_components as dbc @@ -12,6 +11,7 @@ import dash_daq as daq import pandas as pd from flask import jsonify, request, Response as FlaskResponse +from libs.car import Cars from mylogger import logger from trip import Trips @@ -23,6 +23,8 @@ from web.app import app, dash_app, myp, chc from web.db import Database # pylint: disable=invalid-name +from web.figure_filter import Figure_Filter + RESPONSE = "-response" EMPTY_DIV = "empty-div" ABRP_SWITCH = 'abrp-switch' @@ -55,46 +57,9 @@ def diff_dashtable(data, data_previous, row_id_name="row_id"): return changes -figures_list = ["consumption_fig", "consumption_fig_by_speed", "consumption_graph_by_temp", "trips_map"] -y_list = [["consumption_km"], ["consumption_km", "consumption_km"], ["consumption_km", "consumption_km"], - ["long", "start_at"]] -x_list = ["start_at", "speed", "consumption_by_temp", "lat"] -outputs = [Output(id, "figure") for id in figures_list] - - -dash_app.clientside_callback( - """ - function(data,range, figures) { - return filter_dataset(data,range,figures,%s, %s); - } - """ % (x_list, y_list), - *outputs, - Input('clientside-data-store', 'data'), - Input('date-slider', 'value'), - Input('clientside-figure-store', 'data')) - def create_callback(): # noqa: MC0001 global CALLBACK_CREATED if not CALLBACK_CREATED: - @dash_app.callback(Output('summary-cards', 'children'), - Output('tab_trips_fig', 'children'), - Output('tab_charge', 'children'), - Output('date-slider', 'max'), - Output('date-slider', 'step'), - Output('date-slider', 'marks'), - Input('date-slider', 'value')) - def display_value(value): # pylint: disable=unused-variable - mini = datetime.fromtimestamp(value[0], tz=timezone.utc) - maxi = datetime.fromtimestamp(value[1], tz=timezone.utc) - filtered_trips = Trips() - for trip in trips: - if mini <= trip.start_at <= maxi: - filtered_trips.append(trip) - filtered_chargings = Charging.get_chargings(mini, maxi) - figures.get_figures(filtered_trips, filtered_chargings) - return create_card(figures.SUMMARY_CARDS), \ - figures.table_fig, figures.battery_table, max_millis, step, marks - @dash_app.callback(Output(EMPTY_DIV, "children"), [Input("battery-table", "data_timestamp")], [State("battery-table", "data"), @@ -106,13 +71,13 @@ def create_callback(): # noqa: MC0001 for changed_line in diff_data: if changed_line['column_name'] == 'price': conn = Database.get_db() - if not Database.set_chargings_price( conn, changed_line['start_at'], + if not Database.set_chargings_price(conn, changed_line['start_at'], changed_line['current_value']): logger.error("Can't find line to update in the database") conn.close() return "" - @dash_app.callback([Output("tab_battery_popup_graph", "children"), Output("tab_battery_popup", "is_open"), ], + @dash_app.callback([Output("tab_battery_popup_graph", "children"), Output("tab_battery_popup", "is_open")], [Input("battery-table", "active_cell"), Input("tab_battery_popup-close", "n_clicks")], [State('battery-table', 'data'), @@ -129,7 +94,7 @@ def create_callback(): # noqa: MC0001 [Input("trips-table", "active_cell"), Input("tab_trips_popup-close", "n_clicks")], State("tab_trips_popup", "is_open")) - def get_altitude(active_cell, close, is_open): # pylint: disable=unused-argument, unused-variable + def get_altitude_graph(active_cell, close, is_open): # pylint: disable=unused-argument, unused-variable if is_open is None: is_open = False if active_cell is not None and active_cell["column_id"] in ["altitude_diff"] and not is_open: @@ -275,12 +240,14 @@ def update_trips(): conn.close() min_date = None max_date = None + car = myp.vehicles_list[0] # todo handle multiple car try: - trips_by_vin = Trips.get_trips(myp.vehicles_list) - trips = next(iter(trips_by_vin.values())) # todo handle multiple car + trips_by_vin = Trips.get_trips(Cars([car])) + trips = trips_by_vin[car.vin] assert len(trips) > 0 min_date = trips[0].start_at max_date = trips[-1].start_at + figures.get_figures(trips[0].car) except (StopIteration, AssertionError): logger.debug("No trips yet") try: @@ -304,6 +271,7 @@ def update_trips(): step = (max_millis - min_millis) / 100 marks = figures.get_marks_from_start_end(min_date, max_date) cached_layout = None # force regenerate layout + figures.get_figures(car) except (ValueError, IndexError): logger.error("update_trips (slider): %s", exc_info=True) except AttributeError: @@ -333,14 +301,17 @@ def __get_control_tabs(): def create_card(card: dict): res = [] for tile, value in card.items(): - text = value["text"] + rows = value["text"] # if isinstance(text, str): # text = html.H3(text) + html_text = [] + for row in rows: + html_text.append(html.Div(row, className="d-flex flex-row justify-content-center")) res.append(html.Div( dbc.Card([ html.H4(tile, className="card-title text-center"), dbc.Row([ - dbc.Col(dbc.CardBody(text, style={"whiteS pace": "nowrap", "fontSize": "160%"}), + dbc.Col(dbc.CardBody(html_text, style={"whiteSpace": "nowrap", "fontSize": "160%"}), className="text-center"), dbc.Col(dbc.CardImg(src=value.get("src", Component.UNDEFINED), style={"maxHeight": "7rem"})) ], @@ -355,16 +326,23 @@ def serve_layout(): global cached_layout if cached_layout is None: logger.debug("Create new layout") + fig_filter = Figure_Filter() try: - figures.get_figures(trips, chargings) - summary_tab = [dbc.Container(dbc.Row(id="summary-cards", - children=create_card(figures.SUMMARY_CARDS)), fluid=True), - dcc.Graph(id="consumption_fig"), - dcc.Graph(id="consumption_fig_by_speed"), - dcc.Graph(id="consumption_graph_by_temp", - style={'display': 'none'} if figures.consumption_fig_by_temp is None else {}, - )] - maps = dcc.Graph(id="trips_map", style={"height": '90vh'}) + summary_tab = [ + dbc.Container(dbc.Row(id="summary-cards", + children=create_card(figures.SUMMARY_CARDS)), fluid=True), + fig_filter.add_graph(dcc.Graph(id="consumption_fig"), "start_at", ["consumption_km"], + figures.consumption_fig), + fig_filter.add_graph(dcc.Graph(id="consumption_fig_by_speed"), "speed_average", + ["consumption_km"] * 2, figures.consumption_fig_by_speed), + fig_filter.add_graph(dcc.Graph(id="consumption_graph_by_temp"), "consumption_by_temp", + ["consumption_km"] * 2, figures.consumption_fig_by_temp)] + maps = fig_filter.add_map(dcc.Graph(id="trips_map", style={"height": '90vh'}), "lat", + ["long", "start_at"], figures.trips_map) + fig_filter.add_table("trips", figures.table_fig) + fig_filter.add_table("chargings", figures.battery_table) + fig_filter.src = {"trips": trips.get_trips_as_dict(), "chargings": chargings} + dash_app.clientside_callback(*fig_filter.get_clientside_callback()) create_callback() range_slider = dcc.RangeSlider( id='date-slider', @@ -380,9 +358,7 @@ def serve_layout(): logger.warning("Failed to generate figure, there is probably not enough data yet", exc_info_debug=True) range_slider = html.Div() data_div = html.Div([ - dcc.Store(id='clientside-figure-store', data=[figures.consumption_fig, figures.consumption_fig_by_speed, - figures.consumption_fig_by_temp, figures.trips_map]), - dcc.Store(id='clientside-data-store', data=figures.consumption_df_dict), + *fig_filter.get_store(), range_slider, html.Div([ dbc.Tabs([