Attempt to support .gpx files.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4389 ed5197a5-7fde-0310-b194-c3ffbd925b24
This commit is contained in:
parent
e6fd33b969
commit
39927e75f2
81
apps/gg/gpx.js
Normal file
81
apps/gg/gpx.js
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
function xml_parse(xml) {
|
||||||
|
let result;
|
||||||
|
let path = [];
|
||||||
|
let tag_begin;
|
||||||
|
let text_begin;
|
||||||
|
for (let i = 0; i < xml.length; i++) {
|
||||||
|
let c = xml.charAt(i);
|
||||||
|
if (!tag_begin && c == '<') {
|
||||||
|
if (i > text_begin && path.length) {
|
||||||
|
let value = xml.substring(text_begin, i);
|
||||||
|
if (!/^\s*$/.test(value)) {
|
||||||
|
path[path.length - 1].value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tag_begin = i + 1;
|
||||||
|
} else if (tag_begin && c == '>') {
|
||||||
|
let tag = xml.substring(tag_begin, i).trim();
|
||||||
|
if (tag.startsWith('?') && tag.endsWith('?')) {
|
||||||
|
/* Ignore directives. */
|
||||||
|
} else if (tag.startsWith('/')) {
|
||||||
|
path.pop();
|
||||||
|
} else {
|
||||||
|
let parts = tag.split(' ');
|
||||||
|
let attributes = {};
|
||||||
|
for (let j = 1; j < parts.length; j++) {
|
||||||
|
let eq = parts[j].indexOf('=');
|
||||||
|
let value = parts[j].substring(eq + 1);
|
||||||
|
if (value.startsWith('"') && value.endsWith('"')) {
|
||||||
|
value = value.substring(1, value.length - 1);
|
||||||
|
}
|
||||||
|
attributes[parts[j].substring(0, eq)] = value;
|
||||||
|
}
|
||||||
|
let next = {name: parts[0], children: [], attributes: attributes};
|
||||||
|
if (path.length) {
|
||||||
|
path[path.length - 1].children.push(next);
|
||||||
|
} else {
|
||||||
|
result = next;
|
||||||
|
}
|
||||||
|
if (!tag.endsWith('/')) {
|
||||||
|
path.push(next);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tag_begin = undefined;
|
||||||
|
text_begin = i + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function* xml_each(node, name) {
|
||||||
|
for (let child of node.children) {
|
||||||
|
if (child.name == name) {
|
||||||
|
yield child;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function gpx_parse(xml) {
|
||||||
|
let result = {segments: []};
|
||||||
|
let tree = xml_parse(xml);
|
||||||
|
if (tree?.name == 'gpx') {
|
||||||
|
for (let trk of xml_each(tree, 'trk')) {
|
||||||
|
for (let trkseg of xml_each(trk, 'trkseg')) {
|
||||||
|
let segment = [];
|
||||||
|
for (let trkpt of xml_each(trkseg, 'trkpt')) {
|
||||||
|
segment.push({lat: parseFloat(trkpt.attributes.lat), lon: parseFloat(trkpt.attributes.lon)});
|
||||||
|
}
|
||||||
|
result.segments.push(segment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (let metadata of xml_each(tree, 'metadata')) {
|
||||||
|
for (let link of xml_each(metadata, 'link')) {
|
||||||
|
result.link = link.attributes.href;
|
||||||
|
}
|
||||||
|
for (let time of xml_each(metadata, 'time')) {
|
||||||
|
result.time = time.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
import {LitElement, html, unsafeHTML, css, guard, until} from './lit-all.min.js';
|
import {LitElement, html, unsafeHTML, css, guard, until} from './lit-all.min.js';
|
||||||
import * as tfrpc from '/static/tfrpc.js';
|
import * as tfrpc from '/static/tfrpc.js';
|
||||||
import * as polyline from './polyline.js';
|
import * as polyline from './polyline.js';
|
||||||
|
import {gpx_parse} from './gpx.js';
|
||||||
|
|
||||||
const k_client_id = '28276';
|
const k_client_id = '28276';
|
||||||
const k_redirect_url = 'https://tildefriends.net/~cory/gg/login';
|
const k_redirect_url = 'https://tildefriends.net/~cory/gg/login';
|
||||||
@ -75,7 +76,12 @@ class GgAppElement extends LitElement {
|
|||||||
`, []);
|
`, []);
|
||||||
for (let [index, row] of blob_ids.entries()) {
|
for (let [index, row] of blob_ids.entries()) {
|
||||||
this.status = {text: 'loading activity data', value: index, max: blob_ids.length};
|
this.status = {text: 'loading activity data', value: index, max: blob_ids.length};
|
||||||
this.loaded_activities.push(JSON.parse(await tfrpc.rpc.get_blob(row.blob_id)));
|
let blob = await tfrpc.rpc.get_blob(row.blob_id);
|
||||||
|
try {
|
||||||
|
this.loaded_activities.push(JSON.parse(blob));
|
||||||
|
} catch {
|
||||||
|
this.loaded_activities.push(gpx_parse(blob));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.status = undefined;
|
this.status = undefined;
|
||||||
this.update_map();
|
this.update_map();
|
||||||
@ -208,6 +214,41 @@ class GgAppElement extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
activity_bounds(activity) {
|
||||||
|
let min_lat = Number.MAX_VALUE;
|
||||||
|
let min_lon = Number.MAX_VALUE;
|
||||||
|
let max_lat = -Number.MAX_VALUE;
|
||||||
|
let max_lon = -Number.MAX_VALUE;
|
||||||
|
if (activity?.map?.polyline) {
|
||||||
|
for (let pt of polyline.decode(activity.map.polyline)) {
|
||||||
|
min_lat = Math.min(min_lat, pt[0]);
|
||||||
|
min_lon = Math.min(min_lon, pt[1]);
|
||||||
|
max_lat = Math.max(max_lat, pt[0]);
|
||||||
|
max_lon = Math.max(max_lon, pt[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (activity?.segments) {
|
||||||
|
for (let segment of activity.segments) {
|
||||||
|
for (let pt of segment) {
|
||||||
|
min_lat = Math.min(min_lat, pt.lat);
|
||||||
|
min_lon = Math.min(min_lon, pt.lon);
|
||||||
|
max_lat = Math.max(max_lat, pt.lat);
|
||||||
|
max_lon = Math.max(max_lon, pt.lon);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
min: {
|
||||||
|
lat: min_lat,
|
||||||
|
lng: min_lon,
|
||||||
|
},
|
||||||
|
max: {
|
||||||
|
lat: max_lat,
|
||||||
|
lng: max_lon,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
async update_map() {
|
async update_map() {
|
||||||
if (!this.leaflet) {
|
if (!this.leaflet) {
|
||||||
this.leaflet = L.map('map', {attributionControl: false, maxZoom: 16, bounceAtZoomLimits: false});
|
this.leaflet = L.map('map', {attributionControl: false, maxZoom: 16, bounceAtZoomLimits: false});
|
||||||
@ -257,12 +298,11 @@ class GgAppElement extends LitElement {
|
|||||||
this.grid_layer.addTo(this.leaflet);
|
this.grid_layer.addTo(this.leaflet);
|
||||||
}
|
}
|
||||||
for (let activity of this.loaded_activities) {
|
for (let activity of this.loaded_activities) {
|
||||||
for (let pt of polyline.decode(activity.map.polyline)) {
|
let bounds = this.activity_bounds(activity);
|
||||||
this.min_lat = Math.min(this.min_lat, pt[0]);
|
this.min_lat = Math.min(this.min_lat, bounds.min.lat);
|
||||||
this.min_lon = Math.min(this.min_lon, pt[1]);
|
this.min_lon = Math.min(this.min_lon, bounds.min.lng);
|
||||||
this.max_lat = Math.max(this.max_lat, pt[0]);
|
this.max_lat = Math.max(this.max_lat, bounds.max.lat);
|
||||||
this.max_lon = Math.max(this.max_lon, pt[1]);
|
this.max_lon = Math.max(this.max_lon, bounds.max.lng);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
this.leaflet.fitBounds([
|
this.leaflet.fitBounds([
|
||||||
[this.min_lat, this.min_lon],
|
[this.min_lat, this.min_lon],
|
||||||
@ -392,6 +432,7 @@ class GgAppElement extends LitElement {
|
|||||||
|
|
||||||
draw_activity_to_tile(image_data, width, height, ul, lr, activity) {
|
draw_activity_to_tile(image_data, width, height, ul, lr, activity) {
|
||||||
let color = this.activity_to_color(activity);
|
let color = this.activity_to_color(activity);
|
||||||
|
if (activity?.map?.polyline) {
|
||||||
let last;
|
let last;
|
||||||
for (let pt of polyline.decode(activity.map.polyline)) {
|
for (let pt of polyline.decode(activity.map.polyline)) {
|
||||||
let px = [
|
let px = [
|
||||||
@ -404,6 +445,58 @@ class GgAppElement extends LitElement {
|
|||||||
last = px;
|
last = px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (activity?.segments) {
|
||||||
|
console.log('have a gpx');
|
||||||
|
for (let segment of activity.segments) {
|
||||||
|
let last;
|
||||||
|
for (let pt of segment) {
|
||||||
|
let px = [
|
||||||
|
Math.floor(width * (pt.lon - ul.lng) / (lr.lng - ul.lng)),
|
||||||
|
Math.floor(height * (pt.lat - ul.lat) / (lr.lat - ul.lat)),
|
||||||
|
];
|
||||||
|
if (last) {
|
||||||
|
this.line(image_data, last[0], last[1], px[0], px[1], color);
|
||||||
|
}
|
||||||
|
last = px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async on_upload(event) {
|
||||||
|
let file = event.srcElement.files[0];
|
||||||
|
let xml = await file.text();
|
||||||
|
let gpx = gpx_parse(xml);
|
||||||
|
let blob_id = await tfrpc.rpc.store_blob(xml);
|
||||||
|
console.log('blob_id = ', blob_id);
|
||||||
|
let message = {
|
||||||
|
type: 'gg-activity',
|
||||||
|
mentions: [
|
||||||
|
{
|
||||||
|
link: `https://${gpx.link}/activity/${gpx.time}`,
|
||||||
|
name: 'activity_url',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
link: blob_id,
|
||||||
|
name: 'activity_data',
|
||||||
|
}
|
||||||
|
],
|
||||||
|
};
|
||||||
|
console.log('message = ', message);
|
||||||
|
try {
|
||||||
|
let id = await tfrpc.rpc.appendMessage(this.id, message);
|
||||||
|
console.log('appended message', id);
|
||||||
|
} catch (e) {
|
||||||
|
console.log('augh', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
upload() {
|
||||||
|
let input = document.createElement('input');
|
||||||
|
input.type = 'file';
|
||||||
|
input.onchange = this.on_upload;
|
||||||
|
input.click();
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (!this.user?.credentials?.session?.name) {
|
if (!this.user?.credentials?.session?.name) {
|
||||||
@ -415,8 +508,14 @@ class GgAppElement extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<h1>Welcome, ${this.user.credentials.session.name} <span style="font-size: xx-small">${this.id}</span></h1>
|
<div>
|
||||||
|
<h1>
|
||||||
|
Welcome, ${this.user.credentials.session.name}
|
||||||
|
<span style="font-size: xx-small">${this.id}</span>
|
||||||
|
<input type="button" value="📁" @click=${this.upload}></input>
|
||||||
|
</h1>
|
||||||
<h3>${this.status?.text} <progress ?hidden=${!this.status?.max} value=${this.status?.value} max=${this.status?.max}>${this.status?.value}</progress></h3>
|
<h3>${this.status?.text} <progress ?hidden=${!this.status?.max} value=${this.status?.value} max=${this.status?.max}>${this.status?.value}</progress></h3>
|
||||||
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user