Migrate otisserv to pub (#205)

Migrate otisserv to pub
This commit is contained in:
Jason Wen
2023-09-06 01:06:02 -04:00
committed by GitHub
parent 7dbbc001eb
commit e0cc25c3e2
14 changed files with 920 additions and 0 deletions
+420
View File
@@ -0,0 +1,420 @@
#!/usr/bin/env python3.8
# The MIT License
#
# Copyright (c) 2019-, Rick Lan, dragonpilot community, and a number of other of contributors.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
from http.server import BaseHTTPRequestHandler, HTTPServer
from cgi import parse_header, parse_multipart
from urllib.parse import parse_qs, unquote
import json
import requests
import math
from common.basedir import BASEDIR
from common.params import Params
params = Params()
hostName = ""
serverPort = 8082
pi = 3.1415926535897932384626
x_pi = 3.14159265358979324 * 3000.0 / 180.0
a = 6378245.0
ee = 0.00669342162296594323
class OtisServ(BaseHTTPRequestHandler):
def do_GET(self):
use_amap = params.get_bool("EnableAmap")
use_gmap = not use_amap and params.get_bool("EnableGmap")
if self.path == '/logo.png':
self.get_logo()
return
if self.path == '/?reset=1':
params.put("NavDestination", "")
if use_amap:
if self.path == '/style.css':
self.send_response(200)
self.send_header("Content-type", "text/css")
self.end_headers()
self.get_amap_css()
return
elif self.path == '/index.js':
self.send_response(200)
self.send_header("Content-type", "text/javascript")
self.end_headers()
self.get_amap_js()
return
else:
self.send_response(200)
self.send_header("Content-type", "text/html")
self.end_headers()
if self.get_amap_key() is None or self.get_amap_key_2() is None:
self.display_page_amap_key()
return
if self.get_app_token() is None:
self.display_page_app_token()
return
self.display_page_amap()
elif use_gmap:
if self.path == '/style.css':
self.send_response(200)
self.send_header("Content-type", "text/css")
self.end_headers()
self.get_gmap_css()
return
elif self.path == '/index.js':
self.send_response(200)
self.send_header("Content-type", "text/javascript")
self.end_headers()
self.get_gmap_js()
return
else:
self.send_response(200)
self.send_header("Content-type", "text/html")
self.end_headers()
if self.get_gmap_key() is None:
self.display_page_gmap_key()
return
if self.get_app_token() is None:
self.display_page_app_token()
return
self.display_page_gmap()
else:
self.send_response(200)
self.send_header("Content-type", "text/html")
self.end_headers()
if self.get_public_token() is None:
self.display_page_public_token()
return
if self.get_app_token() is None:
self.display_page_app_token()
return
self.display_page_addr_input()
def do_POST(self):
use_amap = params.get_bool("EnableAmap")
use_gmap = not use_amap and params.get_bool("EnableGmap")
postvars = self.parse_POST()
self.send_response(200)
self.send_header("Content-type", "text/html")
self.end_headers()
if use_amap:
# amap token
if self.get_amap_key() is None or self.get_amap_key_2() is None:
if postvars is None or \
("amap_key_val" not in postvars or postvars.get("amap_key_val")[0] == "") or \
("amap_key_val_2" not in postvars or postvars.get("amap_key_val_2")[0] == ""):
self.display_page_amap_key()
return
params.put("AmapKey1", postvars.get("amap_key_val")[0])
params.put("AmapKey2", postvars.get("amap_key_val_2")[0])
elif use_gmap:
# gmap token
if self.get_gmap_key() is None:
if postvars is None or "gmap_key_val" not in postvars or postvars.get("gmap_key_val")[0] == "":
self.display_page_gmap_key()
return
params.put("GmapKey", postvars.get("gmap_key_val")[0])
else:
# mapbox public key
if self.get_public_token() is None:
if postvars is None or "pk_token_val" not in postvars or postvars.get("pk_token_val")[0] == "":
self.display_page_public_token()
return
token = postvars.get("pk_token_val")[0]
if "pk." not in token:
self.display_page_public_token("Your token was incorrect!")
return
params.put('CustomMapboxTokenPk', token)
# app key
if self.get_app_token() is None:
if postvars is None or "sk_token_val" not in postvars or postvars.get("sk_token_val")[0] == "":
self.display_page_app_token()
return
token = postvars.get("sk_token_val")[0]
if "sk." not in token:
self.display_page_app_token("Your token was incorrect!")
return
params.put('CustomMapboxTokenSk', token)
# nav confirmed
if postvars is not None:
if "lat" in postvars and postvars.get("lat")[0] != "" and "lon" in postvars and postvars.get("lon")[0] != "":
lat = float(postvars.get("lat")[0])
lng = float(postvars.get("lon")[0])
save_type = postvars.get("save_type")[0]
name = postvars.get("name")[0] if postvars.get("name") is not None else ""
if use_amap:
lng, lat = self.gcj02towgs84(lng, lat)
params.put('NavDestination', "{\"latitude\": %f, \"longitude\": %f, \"place_name\": \"%s\"}" % (lat, lng, name))
self.to_json(lat, lng, save_type, name)
# favorites
if not use_gmap and "fav_val" in postvars:
addr = postvars.get("fav_val")[0]
real_addr = None
lon = None
lat = None
if addr != "favorites":
val = params.get("ApiCache_NavDestinations", encoding='utf8')
if val is not None:
val = val.rstrip('\x00')
dests = json.loads(val)
for item in dests:
if "label" in item and item["label"] == addr:
lat = item["latitude"]
lon = item["longitude"]
real_addr = item["place_name"]
break
else:
real_addr = None
if real_addr is not None:
self.display_page_nav_confirmation(real_addr, lon, lat)
return
else:
self.display_page_addr_input("Place Not Found")
return
# search
if not use_gmap and "addr_val" in postvars:
addr = postvars.get("addr_val")[0]
if addr != "":
real_addr, lat, lon = self.query_addr(addr)
if real_addr is not None:
self.display_page_nav_confirmation(real_addr, lon, lat)
return
else:
self.display_page_addr_input("Place Not Found")
return
if use_amap:
self.display_page_amap()
elif use_gmap:
self.display_page_gmap()
else:
self.display_page_addr_input()
def get_logo(self):
self.send_response(200)
self.send_header('Content-type','image/png')
self.end_headers()
f = open("%s/selfdrive/assets/img_spinner_comma.png" % BASEDIR, "rb")
self.wfile.write(f.read())
f.close()
def get_gmap_css(self):
self.wfile.write(bytes(self.get_parsed_template("gmap/style.css"), "utf-8"))
def get_gmap_js(self):
lon, lat = self.get_last_lon_lat()
self.wfile.write(bytes(self.get_parsed_template("gmap/index.js", {"{{lat}}": lat, "{{lon}}": lon}), "utf-8"))
def get_gmap_key(self):
token = params.get("GmapKey", encoding='utf8')
if token is not None and token != "":
return token.rstrip('\x00')
return None
def get_amap_css(self):
self.wfile.write(bytes(self.get_parsed_template("amap/style.css"), "utf-8"))
def get_amap_js(self):
lon, lat = self.get_last_lon_lat()
self.wfile.write(bytes(self.get_parsed_template("amap/index.js", {"{{lat}}": lat, "{{lon}}": lon}), "utf-8"))
def get_amap_key(self):
token = params.get("AmapKey1", encoding='utf8')
if token is not None and token != "":
return token.rstrip('\x00')
return None
def get_amap_key_2(self):
token = params.get("AmapKey2", encoding='utf8')
if token is not None and token != "":
return token.rstrip('\x00')
return None
def get_public_token(self):
token = params.get("CustomMapboxTokenPk", encoding='utf8')
if token is not None and token != "":
return token.rstrip('\x00')
return None
def get_app_token(self):
token = params.get("CustomMapboxTokenSk", encoding='utf8')
if token is not None and token != "":
return token.rstrip('\x00')
return None
def get_last_lon_lat(self):
last_pos = Params().get("LastGPSPosition")
if last_pos is not None and last_pos != "":
l = json.loads(last_pos)
return l["longitude"], l["latitude"]
return "", ""
def display_page_gmap_key(self):
self.wfile.write(bytes(self.get_parsed_template("body", {"{{content}}": self.get_parsed_template("gmap/key_input")}), "utf-8"))
def display_page_amap_key(self):
self.wfile.write(bytes(self.get_parsed_template("body", {"{{content}}": self.get_parsed_template("amap/key_input")}), "utf-8"))
def display_page_public_token(self, msg = ""):
self.wfile.write(bytes(self.get_parsed_template("body", {"{{content}}": self.get_parsed_template("public_token_input", {"{{msg}}": msg})}), "utf-8"))
def display_page_app_token(self, msg = ""):
self.wfile.write(bytes(self.get_parsed_template("body", {"{{content}}": self.get_parsed_template("app_token_input", {"{{msg}}": msg})}), "utf-8"))
def display_page_addr_input(self, msg = ""):
self.wfile.write(bytes(self.get_parsed_template("body", {"{{content}}": self.get_parsed_template("addr_input", {"{{msg}}": msg})}), "utf-8"))
def display_page_nav_confirmation(self, addr, lon, lat):
content = self.get_parsed_template("addr_input", {"{{msg}}": ""}) + self.get_parsed_template("nav_confirmation", {"{{token}}": self.get_public_token(), "{{lon}}": lon, "{{lat}}": lat, "{{addr}}": addr})
self.wfile.write(bytes(self.get_parsed_template("body", {"{{content}}": content }), "utf-8"))
def display_page_gmap(self):
self.wfile.write(bytes(self.get_parsed_template("gmap/index.html", {"{{gmap_key}}": self.get_gmap_key()}), "utf-8"))
def display_page_amap(self):
self.wfile.write(bytes(self.get_parsed_template("amap/index.html", {"{{amap_key}}": self.get_amap_key(), "{{amap_key_2}}": self.get_amap_key_2()}), "utf-8"))
def get_parsed_template(self, name, replace = {}):
f = open('%s/selfdrive/navd/tpl/%s.tpl' % (BASEDIR, name), mode='r', encoding='utf-8')
content = f.read()
for key in replace:
content = content.replace(key, str(replace[key]))
f.close()
return content
def query_addr(self, addr):
if addr == "":
return None, None, None
query = "https://api.mapbox.com/geocoding/v5/mapbox.places/" + unquote(addr) + ".json?access_token=" + self.get_public_token() + "&limit=1"
# focus on place around last gps position
last_pos = Params().get("LastGPSPosition")
if last_pos is not None and last_pos != "":
l = json.loads(last_pos)
query += "&proximity=%s,%s" % (l["longitude"], l["latitude"])
r = requests.get(query)
if r.status_code != 200:
return None, None, None
j = json.loads(r.text)
if not j["features"]:
return None, None, None
feature = j["features"][0]
return feature["place_name"], feature["center"][1], feature["center"][0]
def parse_POST(self):
ctype, pdict = parse_header(self.headers['content-type'])
if ctype == 'application/x-www-form-urlencoded':
length = int(self.headers['content-length'])
postvars = parse_qs(
self.rfile.read(length).decode('utf-8'),
keep_blank_values=1)
else:
postvars = {}
return postvars
def gcj02towgs84(self, lng, lat):
dlat = self.transform_lat(lng - 105.0, lat - 35.0)
dlng = self.transform_lng(lng - 105.0, lat - 35.0)
radlat = lat / 180.0 * pi
magic = math.sin(radlat)
magic = 1 - ee * magic * magic
sqrtmagic = math.sqrt(magic)
dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * pi)
dlng = (dlng * 180.0) / (a / sqrtmagic * math.cos(radlat) * pi)
mglat = lat + dlat
mglng = lng + dlng
return [lng * 2 - mglng, lat * 2 - mglat]
def transform_lat(self, lng, lat):
ret = -100.0 + 2.0 * lng + 3.0 * lat + 0.2 * lat * lat + 0.1 * lng * lat + 0.2 * math.sqrt(abs(lng))
ret += (20.0 * math.sin(6.0 * lng * pi) + 20.0 * math.sin(2.0 * lng * pi)) * 2.0 / 3.0
ret += (20.0 * math.sin(lat * pi) + 40.0 * math.sin(lat / 3.0 * pi)) * 2.0 / 3.0
ret += (160.0 * math.sin(lat / 12.0 * pi) + 320 * math.sin(lat * pi / 30.0)) * 2.0 / 3.0
return ret
def transform_lng(self, lng, lat):
ret = 300.0 + lng + 2.0 * lat + 0.1 * lng * lng + 0.1 * lng * lat + 0.1 * math.sqrt(abs(lng))
ret += (20.0 * math.sin(6.0 * lng * pi) + 20.0 * math.sin(2.0 * lng * pi)) * 2.0 / 3.0
ret += (20.0 * math.sin(lng * pi) + 40.0 * math.sin(lng / 3.0 * pi)) * 2.0 / 3.0
ret += (150.0 * math.sin(lng / 12.0 * pi) + 300.0 * math.sin(lng / 30.0 * pi)) * 2.0 / 3.0
return ret
def to_json(self, lat, lng, type = "recent", name = ""):
if name == "":
name = str(lat) + "," + str(lng)
new_dest = {"latitude": float(lat), "longitude": float(lng), "place_name": name}
if type == "recent":
new_dest["save_type"] = "recent"
else:
new_dest["save_type"] = "favorite"
new_dest["label"] = type
val = params.get("ApiCache_NavDestinations", encoding='utf8')
if val is not None:
val = val.rstrip('\x00')
dests = [] if val is None else json.loads(val)
# type idx
type_label_ids = {"home": None, "work": None, "fav1": None, "fav2": None, "fav3": None, "recent": []}
idx = 0
for d in dests:
if d["save_type"] == "favorite":
type_label_ids[d["label"]] = idx
else:
type_label_ids["recent"].append(idx)
idx += 1
if type == "recent":
id = None
if len(type_label_ids["recent"]) > 10:
dests.pop(type_label_ids["recent"][-1])
else:
id = type_label_ids[type]
if id is None:
dests.insert(0, new_dest)
else:
dests[id] = new_dest
params.put("ApiCache_NavDestinations", json.dumps(dests).rstrip("\n\r"))
def main():
webServer = HTTPServer((hostName, serverPort), OtisServ)
try:
webServer.serve_forever()
except KeyboardInterrupt:
pass
webServer.server_close()
if __name__ == "__main__":
main()
+16
View File
@@ -0,0 +1,16 @@
<form name="searchForm" method="post">
<fieldset class="uk-fieldset">
<div class="uk-margin">
<select class="uk-select" name="fav_val">
<option value="favorites">Select Saved Destinations</option>
<option value="home">Home</option>
<option value="work">Work</option>
<option value="fav1">Favorite 1</option>
<option value="fav2">Favorite 2</option>
<option value="fav3">Favorite 3</option>
<div style="padding: 5px; color: red; font-weight: bold;" align="center">{{msg}}</div>
<input class="uk-input" type="text" name="addr_val" placeholder="Search a place">
<input class="uk-button uk-button-primary uk-width-1-1 uk-margin-small-bottom" type="submit" value="Search">
</div>
</fieldset>
</form>
+47
View File
@@ -0,0 +1,47 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="initial-scale=1.0, user-scalable=no, width=device-width">
<title>输入提示后查询</title>
<!-- UIkit CSS -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/uikit@3.9.2/dist/css/uikit.min.css" />
<!-- UIkit JS -->
<script src="https://cdn.jsdelivr.net/npm/uikit@3.9.2/dist/js/uikit.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/uikit@3.9.2/dist/js/uikit-icons.min.js"></script>
<link rel="stylesheet" href="./style.css"/>
<script type="text/javascript">
window._AMapSecurityConfig = {
securityJsCode:'{{amap_key_2}}',
}
</script>
<script type="text/javascript"
src="https://webapi.amap.com/maps?v=1.4.2&key={{amap_key}}"></script>
<style type="text/css">
body {
font-size: 12px;
}
</style>
</head>
<body>
<div style="place-items: center; padding: 5px; font-weight: bold;" align="center"><a href="?reset=1"><img style="width: 100px; height: 100px; background-color: black;" src="logo.png"></a></div>
<div class="uk-grid-match uk-grid-small uk-text-center" uk-grid>
<div class="uk-width-1-3@m">
<select id="save_type" class="uk-select">
<option value="recent">最近</option>
<option value="home">住家</option>
<option value="work">工作</option>
</select>
</div>
<div class="uk-width-expand@m">
<input class="uk-input" type="text" id="keyword" name="keyword" placeholder="请输入关键字:(选定后搜索)" onfocus='this.value=""'/>
</div>
</div>
<input type="hidden" id="longitude" />
<input type="hidden" id="latitude" />
<div style="height: 80%" id="container"></div>
<script src="./index.js"></script>
</body>
</html>
+120
View File
@@ -0,0 +1,120 @@
var windowsArr = [];
var markers = [];
var map = new AMap.Map("container", {
resizeEnable: true,
center: [{{lon}}, {{lat}}], //
zoom: 13, //
keyboardEnable: false,
});
var infoWindow;
function openInfo(name, addr, lng, lat) {
//
var info = [];
info.push('<div class="uk-card uk-card-default uk-card-body">');
info.push('<a class="uk-card-badge uk-label" onClick="javascript:infoWindow.close()" uk-close></a>');
info.push("<h3 style=\"padding-top: 10px;\" class=\"uk-card-title\">" + name + "</h3>");
info.push("<p>" + addr + "</p>");
info.push('<div class="uk-card-footer">');
info.push('<form name="navForm" method="post">');
info.push(' <input type="hidden" name="lat" value="' + lat + '">');
info.push(' <input type="hidden" name="lon" value="' + lng + '">');
info.push(' <input type="hidden" name="save_type" value="' + document.getElementById("save_type").value + '">');
info.push(' <input type="hidden" name="name" value="' + name + '">');
info.push(' <input class="uk-button uk-button-primary" type="submit" value="导航" >');
info.push('</form>');
info.push('</div>');
info.push("</div>");
var pos = new AMap.LngLat(lng, lat)
infoWindow = new AMap.InfoWindow({
position: pos,
isCustom: true,
offset: new AMap.Pixel(0, -30),
content: info.join(""), //使
});
infoWindow.open(map, pos);
}
AMap.plugin(["AMap.Autocomplete", "AMap.PlaceSearch"], function () {
var autoOptions = {
city: "全国", //
input: "keyword", //使input的id
};
autocomplete = new AMap.Autocomplete(autoOptions);
var placeSearch = new AMap.PlaceSearch({
map: "",
});
AMap.event.addListener(autocomplete, "select", function (e) {
//TODO poi实现自己的功能
//begin=====
placeSearch.setCity(e.poi.adcode);
if (e.poi && e.poi.location) {
map.setZoom(17);
map.setCenter(e.poi.location);
}
placeSearch.search(e.poi.name, check_dest); //
function check_dest(status, result) {
if (status === "complete" && result.info === "OK") {
for (var h = 0; h < result.poiList.pois.length; h++) {
//marker
var jy = result.poiList.pois[h]["location"]; //
var name = result.poiList.pois[h]["name"]; //
marker = new AMap.Marker({
//
map: map,
position: jy,
});
marker.extData = {
getLng: jy["lng"],
getLat: jy["lat"],
name: name,
address: result.poiList.pois[h]["address"],
}; //
marker.on("click", function (e) {
var hs = e.target.extData;
var content = openInfo(
hs["name"],
hs["address"],
hs["getLng"],
hs["getLat"]
);
});
markers.push(marker);
}
}
}
//end=====
});
});
var clickEventListener = map.on('click', function(e) {
map.remove(markers);
document.getElementById('longitude').value = e.lnglat.getLng();
document.getElementById('latitude').value = e.lnglat.getLat();
lnglatXY = [e.lnglat.getLng(), e.lnglat.getLat()];
var marker = new AMap.Marker({
//
map: map,
position: lnglatXY,
});
marker.extData = {
getLng: e.lnglat.getLng(),
getLat: e.lnglat.getLat(),
}; //
marker.on("click", function (e) {
var hs = e.target.extData;
var content = openInfo(
"",
"(" + hs["getLat"] + ", " + hs["getLng"] + ")",
hs["getLng"],
hs["getLat"]
);
});
markers.push(marker);
if (typeof(infoWindow) != "undefined") {
infoWindow.close();
}
});
+11
View File
@@ -0,0 +1,11 @@
<form name="setAmapTokenForm" method="post">
<fieldset class="uk-fieldset">
<legend class="uk-legend">请输入您的高德地图 API KEY</legend>
<div style="color: red">因系统升级,若于 2021/12/02 前申请 key 的人请重新申请新的「<b>key</b>」和「<b>安全密钥</b>」配对。</div>
<div class="uk-margin">
<input class="uk-input" type="text" name="amap_key_val" placeholder="KEY">
<input class="uk-input" type="text" name="amap_key_val_2" placeholder="安全密钥">
<input class="uk-button uk-button-primary uk-width-1-1 uk-margin-small-bottom" type="submit" value="设置">
</div>
</fieldset>
</form>
+40
View File
@@ -0,0 +1,40 @@
html, body {
margin: 0;
height: 100%;
width: 100%;
position: absolute;
}
#mapContainer {
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100%;
height: 100%;
}
.button-group {
position: absolute;
bottom: 20px;
right: 20px;
font-size: 12px;
padding: 10px;
}
.button-group .button {
height: 28px;
line-height: 28px;
background-color: #0D9BF2;
color: #FFF;
border: 0;
outline: none;
padding-left: 5px;
padding-right: 5px;
border-radius: 3px;
margin-bottom: 4px;
cursor: pointer;
}
.amap-info-content {
font-size: 12px;
}
+10
View File
@@ -0,0 +1,10 @@
<form name="setSkTokenForm" method="post">
<fieldset class="uk-fieldset">
<legend class="uk-legend">Set your Mapbox <b>APP TOKEN</b></legend>
<div style="padding: 5px; color: red; font-weight: bold;">{{msg}}</div>
<div class="uk-margin">
<input class="uk-input" type="text" name="sk_token_val" placeholder="e.g.: sk.xxxxxxx...">
<input class="uk-button uk-button-primary uk-width-1-1 uk-margin-small-bottom" type="submit" value="Set">
</div>
</fieldset>
</form>
+20
View File
@@ -0,0 +1,20 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>sunnypilot</title>
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no">
<!-- UIkit CSS -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/uikit@3.9.2/dist/css/uikit.min.css" />
<!-- UIkit JS -->
<script src="https://cdn.jsdelivr.net/npm/uikit@3.9.2/dist/js/uikit.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/uikit@3.9.2/dist/js/uikit-icons.min.js"></script>
</head>
<body style="margin: 0; padding: 0;">
<div style="display: grid; place-items: center;">
<div style="padding: 5px; font-weight: bold;" align="center"><a href="?reset=1"><img style="width: 100px; height: 100px; background-color: black;" src="logo.png"></a></div>
{{content}}
</div>
</body>
</html>
+39
View File
@@ -0,0 +1,39 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>sunnypilot</title>
<script src="https://polyfill.io/v3/polyfill.min.js?features=default"></script>
<link rel="stylesheet" type="text/css" href="./style.css" />
<!-- UIkit CSS -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/uikit@3.9.2/dist/css/uikit.min.css" />
<!-- UIkit JS -->
<script src="https://cdn.jsdelivr.net/npm/uikit@3.9.2/dist/js/uikit.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/uikit@3.9.2/dist/js/uikit-icons.min.js"></script>
<script src="./index.js"></script>
<meta name="viewport" content="width=device-width">
</head>
<body>
<div style="place-items: center; padding: 5px; font-weight: bold;" align="center"><a href="?reset=1"><img style="width: 100px; height: 100px; background-color: black;" src="logo.png"></a></div>
<div class="uk-grid-match uk-grid-small uk-text-center" uk-grid>
<div class="uk-width-1-3@m">
<select id="save_type" class="uk-select" name="type">
<option value="recent">Recent</option>
<option value="home">Home</option>
<option value="work">Work</option>
</select>
</div>
<div class="uk-width-expand@m">
<input class="uk-input" type="text" id="pac-input" name="keyword" placeholder="Search a place" onfocus='this.value=""'/>
</div>
</div>
<div id="map"></div>
<!-- Async script executes immediately and must be after any DOM elements used in callback. -->
<script
src="https://maps.googleapis.com/maps/api/js?key={{gmap_key}}&callback=initAutocomplete&libraries=places&v=weekly"
async
></script>
</body>
</html>
+86
View File
@@ -0,0 +1,86 @@
// This example adds a search box to a map, using the Google Place Autocomplete
// feature. People can enter geographical searches. The search box will return a
// pick list containing a mix of places and predicted search terms.
// This example requires the Places library. Include the libraries=places
// parameter when you first load the API. For example:
// <script src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&libraries=places">
function initAutocomplete() {
const map = new google.maps.Map(document.getElementById("map"), {
center: { lat: {{lat}}, lng: {{lon}} },
zoom: 13,
mapTypeId: "roadmap",
disableDefaultUI: true
});
// Create the search box and link it to the UI element.
const input = document.getElementById("pac-input");
const searchBox = new google.maps.places.SearchBox(input);
// Bias the SearchBox results towards current map's viewport.
map.addListener("bounds_changed", () => {
searchBox.setBounds(map.getBounds());
});
let markers = [];
// Listen for the event fired when the user selects a prediction and retrieve
// more details for that place.
searchBox.addListener("places_changed", () => {
const places = searchBox.getPlaces();
if (places.length == 0) {
return;
}
// Clear out the old markers.
markers.forEach((marker) => {
marker.setMap(null);
});
markers = [];
// For each place, get the icon, name and location.
const bounds = new google.maps.LatLngBounds();
places.slice(-1).forEach((place) => {
if (!place.geometry || !place.geometry.location) {
console.log("Returned place contains no geometry");
return;
}
const icon = {
url: place.icon,
size: new google.maps.Size(71, 71),
origin: new google.maps.Point(0, 0),
anchor: new google.maps.Point(17, 34),
scaledSize: new google.maps.Size(25, 25),
};
// Create a marker for each place.
markers.push(
new google.maps.Marker({
map,
icon,
title: place.name,
position: place.geometry.location,
})
);
// set nav
var http = new XMLHttpRequest();
http.open("POST", "/", true);
http.setRequestHeader("Content-type","application/x-www-form-urlencoded");
var params = "lat=" + place.geometry.location.lat() + "&lon=" + place.geometry.location.lng();
params += "&save_type=" + document.getElementById("save_type").value;
params += "&name=" + place.name;
http.send(params);
if (place.geometry.viewport) {
// Only geocodes have viewport.
bounds.union(place.geometry.viewport);
} else {
bounds.extend(place.geometry.location);
}
return;
});
map.fitBounds(bounds);
});
}
+9
View File
@@ -0,0 +1,9 @@
<form name="setGmapTokenForm" method="post">
<fieldset class="uk-fieldset">
<legend class="uk-legend">Set your Google Map API Key</legend>
<div class="uk-margin">
<input class="uk-input" type="text" name="gmap_key_val" placeholder="Google Map API KEY">
<input class="uk-button uk-button-primary uk-width-1-1 uk-margin-small-bottom" type="submit" value="设置">
</div>
</fieldset>
</form>
+72
View File
@@ -0,0 +1,72 @@
/* Always set the map height explicitly to define the size of the div
* element that contains the map. */
#map {
height: 100%;
}
/* Optional: Makes the sample page fill the window. */
html,
body {
height: 80%;
margin: 0;
padding: 0;
}
#description {
font-family: Roboto;
font-size: 15px;
font-weight: 300;
}
#infowindow-content .title {
font-weight: bold;
}
#infowindow-content {
display: none;
}
#map #infowindow-content {
display: inline;
}
.pac-card {
background-color: #fff;
border: 0;
border-radius: 2px;
box-shadow: 0 1px 4px -1px rgba(0, 0, 0, 0.3);
margin: 10px;
padding: 0 0.5em;
font: 400 18px Roboto, Arial, sans-serif;
overflow: hidden;
font-family: Roboto;
padding: 0;
}
#pac-container {
padding-bottom: 12px;
margin-right: 12px;
}
.pac-controls {
display: inline-block;
padding: 5px 11px;
}
.pac-controls label {
font-family: Roboto;
font-size: 13px;
font-weight: 300;
}
#title {
color: #fff;
background-color: #4d90fe;
font-size: 25px;
font-weight: 500;
padding: 6px 12px;
}
#target {
width: 345px;
}
+20
View File
@@ -0,0 +1,20 @@
<div><img src="https://api.mapbox.com/styles/v1/mapbox/streets-v11/static/pin-s-l+000({{lon}},{{lat}})/{{lon}},{{lat}},14/300x300?access_token={{token}}" /></div>
<div style="padding: 5px; font-size: 10px;">{{addr}}</div>
<form name="navForm" method="post">
<fieldset class="uk-fieldset">
<div class="uk-margin">
<input type="hidden" name="name" value="{{addr}}">
<input type="hidden" name="lat" value="{{lat}}">
<input type="hidden" name="lon" value="{{lon}}">
<select id="save_type" name="save_type" class="uk-select">
<option value="recent">Recent</option>
<option value="home">Set Home</option>
<option value="work">Set Work</option>
<option value="fav1">Set Favorite 1</option>
<option value="fav2">Set Favorite 2</option>
<option value="fav3">Set Favorite 3</option>
</select>
<input class="uk-button uk-button-primary uk-width-1-1 uk-margin-small-bottom" type="submit" value="Start Navigation">
</div>
</fieldset>
</form>
+10
View File
@@ -0,0 +1,10 @@
<form name="setPkTokenForm" method="post">
<fieldset class="uk-fieldset">
<legend class="uk-legend">Set your Mapbox <b>PUBLIC TOKEN</b></legend>
<div style="padding: 5px; color: red; font-weight: bold;">{{msg}}</div>
<div class="uk-margin">
<input class="uk-input" type="text" name="pk_token_val" placeholder="e.g.: pk.xxxxxxx...">
<input class="uk-button uk-button-primary uk-width-1-1 uk-margin-small-bottom" type="submit" value="Set">
</div>
</fieldset>
</form>