mirror of
https://github.com/GoldenCheetah/GoldenCheetah.git
synced 2026-02-13 08:08:42 +00:00
723 lines
24 KiB
C++
723 lines
24 KiB
C++
/*
|
|
* Copyright (c) 2011 Mark Liversedge (liversedge@gmail.com)
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the Free
|
|
* Software Foundation; either version 2 of the License, or (at your option)
|
|
* any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
* more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program; if not, write to the Free Software Foundation, Inc., 51
|
|
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
#include "BingMap.h"
|
|
|
|
#include "RideItem.h"
|
|
#include "RideFile.h"
|
|
#include "IntervalItem.h"
|
|
#include "Context.h"
|
|
#include "Athlete.h"
|
|
#include "Zones.h"
|
|
#include "Settings.h"
|
|
#include "Colors.h"
|
|
#include "Units.h"
|
|
#include "TimeUtils.h"
|
|
#include "HelpWhatsThis.h"
|
|
|
|
#ifdef NOWEBKIT
|
|
#include <QtWebChannel>
|
|
#endif
|
|
|
|
#include <QDebug>
|
|
|
|
BingMap::BingMap(Context *context) : GcChartWindow(context), context(context), range(-1), current(NULL)
|
|
{
|
|
setControls(NULL);
|
|
|
|
setContentsMargins(0,0,0,0);
|
|
layout = new QVBoxLayout();
|
|
layout->setSpacing(0);
|
|
layout->setContentsMargins(2,0,2,2);
|
|
setChartLayout(layout);
|
|
|
|
#ifdef NOWEBKIT
|
|
view = new QWebEngineView(this);
|
|
#else
|
|
view = new QWebView();
|
|
#endif
|
|
|
|
view->setContentsMargins(0,0,0,0);
|
|
view->page()->view()->setContentsMargins(0,0,0,0);
|
|
view->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
|
view->setAcceptDrops(false);
|
|
layout->addWidget(view);
|
|
|
|
HelpWhatsThis *help = new HelpWhatsThis(view);
|
|
view->setWhatsThis(help->getWhatsThisText(HelpWhatsThis::ChartRides_Map));
|
|
|
|
webBridge = new BWebBridge(context, this);
|
|
#ifdef NOWEBKIT
|
|
// file: MyWebEngineView.cpp, MyWebEngineView extends QWebEngineView
|
|
QWebChannel *channel = new QWebChannel(view->page());
|
|
|
|
// set the web channel to be used by the page
|
|
// see http://doc.qt.io/qt-5/qwebenginepage.html#setWebChannel
|
|
view->page()->setWebChannel(channel);
|
|
|
|
// register QObjects to be exposed to JavaScript
|
|
channel->registerObject(QStringLiteral("webBridge"), webBridge);
|
|
#endif
|
|
|
|
connect(this, SIGNAL(rideItemChanged(RideItem*)), this, SLOT(rideSelected()));
|
|
#ifndef NOWEBKIT
|
|
connect(view->page()->mainFrame(), SIGNAL(javaScriptWindowObjectCleared()), this, SLOT(updateFrame()));
|
|
#endif
|
|
connect(context, SIGNAL(intervalsChanged()), webBridge, SLOT(intervalsChanged()));
|
|
connect(context, SIGNAL(intervalSelected()), webBridge, SLOT(intervalsChanged()));
|
|
connect(context, SIGNAL(intervalZoom(IntervalItem*)), this, SLOT(zoomInterval(IntervalItem*)));
|
|
connect(context, SIGNAL(configChanged(qint32)), this, SLOT(configChanged(qint32)));
|
|
|
|
first = true;
|
|
|
|
// get the colors setup for first run
|
|
configChanged(CONFIG_APPEARANCE);
|
|
}
|
|
|
|
BingMap::~BingMap()
|
|
{
|
|
delete webBridge;
|
|
}
|
|
|
|
void
|
|
BingMap::configChanged(qint32)
|
|
{
|
|
setProperty("color", GColor(CPLOTBACKGROUND));
|
|
rideSelected();
|
|
}
|
|
|
|
void
|
|
BingMap::rideSelected()
|
|
{
|
|
// skip display if data drawn or invalid
|
|
if (myRideItem == NULL || !amVisible()) return;
|
|
RideItem * ride = myRideItem;
|
|
if (ride == current || !ride || !ride->ride()) return;
|
|
else current = ride;
|
|
|
|
// Route metadata ...
|
|
setSubTitle(ride->ride()->getTag("Route", tr("Route")));
|
|
|
|
range=-1;
|
|
rideCP=300;
|
|
|
|
if (context->athlete->zones(ride->isRun)) {
|
|
|
|
// get the right range and CP
|
|
range = context->athlete->zones(ride->isRun)->whichRange(ride->dateTime.date());
|
|
if (range >= 0) rideCP = context->athlete->zones(ride->isRun)->getCP(range);
|
|
}
|
|
|
|
loadRide();
|
|
}
|
|
|
|
void BingMap::loadRide()
|
|
{
|
|
createHtml();
|
|
|
|
#ifdef NOWEBKIT
|
|
view->page()->setHtml(currentPage);
|
|
#else
|
|
view->page()->mainFrame()->setHtml(currentPage);
|
|
#endif
|
|
}
|
|
|
|
void BingMap::updateFrame()
|
|
{
|
|
// deleting the web bridge seems to be the only way to
|
|
// reset state between it and the webpage.
|
|
delete webBridge;
|
|
webBridge = new BWebBridge(context, this);
|
|
connect(context, SIGNAL(intervalsChanged()), webBridge, SLOT(intervalsChanged()));
|
|
connect(context, SIGNAL(intervalSelected()), webBridge, SLOT(intervalsChanged()));
|
|
|
|
#ifndef NOWEBKIT
|
|
view->page()->mainFrame()->addToJavaScriptWindowObject("webBridge", webBridge);
|
|
#endif
|
|
}
|
|
|
|
void BingMap::createHtml()
|
|
{
|
|
RideItem * ride = myRideItem;
|
|
currentPage = "";
|
|
double minLat, minLon, maxLat, maxLon;
|
|
minLat = minLon = 1000;
|
|
maxLat = maxLon = -1000; // larger than 360
|
|
|
|
// get bounding co-ordinates for ride
|
|
foreach(RideFilePoint *rfp, myRideItem->ride()->dataPoints()) {
|
|
if (rfp->lat || rfp->lon) {
|
|
minLat = std::min(minLat,rfp->lat);
|
|
maxLat = std::max(maxLat,rfp->lat);
|
|
minLon = std::min(minLon,rfp->lon);
|
|
maxLon = std::max(maxLon,rfp->lon);
|
|
}
|
|
}
|
|
|
|
QColor bgColor = GColor(CPLOTBACKGROUND);
|
|
QColor fgColor = GCColor::invertColor(bgColor);
|
|
|
|
// No GPS data, so sorry no map
|
|
if(!ride || !ride->ride() || ride->ride()->areDataPresent()->lat == false || ride->ride()->areDataPresent()->lon == false) {
|
|
currentPage = QString("<STYLE>BODY { background-color: %1; color: %2 }</STYLE><center>%3</center>").arg(bgColor.name()).arg(fgColor.name()).arg(tr("No GPS Data Present"));
|
|
setIsBlank(true);
|
|
return;
|
|
} else {
|
|
setIsBlank(false);
|
|
}
|
|
|
|
// load the Google Map v3 API
|
|
currentPage = QString("<!DOCTYPE html> \n"
|
|
"<html>\n"
|
|
"<head>\n"
|
|
"<title>Golden Cheetah Map</title>\n"
|
|
|
|
// Load BING Rest API Services
|
|
"<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>\n"
|
|
"<script type=\"text/javascript\" src=\"http://ecn.dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=7.0\"></script>\n");
|
|
|
|
#ifdef NOWEBKIT
|
|
currentPage += QString("<script type=\"text/javascript\" src=\"qrc:///qtwebchannel/qwebchannel.js\"></script>\n");
|
|
#endif
|
|
|
|
currentPage += QString("<script type=\"text/javascript\"> \n"
|
|
"var webBridge; \n"
|
|
"document.addEventListener(\"DOMContentLoaded\", function () { \n"
|
|
#ifdef NOWEBKIT
|
|
"<!-- it's a good idea to initialize webchannel after DOM ready, if the code is going to manipulate the DOM -->\n"
|
|
" new QWebChannel(qt.webChannelTransport, function (channel) { \n"
|
|
" webBridge = channel.objects.webBridge; \n"
|
|
" initialize(); \n"
|
|
" }); \n"
|
|
#else
|
|
" initialize(); \n"
|
|
#endif
|
|
"}); \n"
|
|
"</script>");
|
|
|
|
// colors
|
|
currentPage += QString("<STYLE>BODY { background-color: %1; color: %2 }</STYLE>")
|
|
.arg(bgColor.name()).arg(fgColor.name());
|
|
// local functions
|
|
currentPage += QString("<script type=\"text/javascript\">\n"
|
|
|
|
"var map;\n" // the map object
|
|
"var intervalList;\n" // array of intervals
|
|
"var markerList;\n" // array of markers
|
|
"var polyList;\n" // array of polylines
|
|
|
|
// Draw the entire route, we use a local webbridge
|
|
// to supply the data to a) reduce bandwidth and
|
|
// b) allow local manipulation. This makes the UI
|
|
// considerably more 'snappy'
|
|
"function drawRoute() {\n"
|
|
#ifdef NOWEBKIT
|
|
// load the GPS co-ordinates
|
|
" webBridge.getLatLons(0, drawRouteForLatLons);\n"
|
|
#else
|
|
// load the GPS co-ordinates
|
|
" var latlons = webBridge.getLatLons(0);\n" // interval "0" is the entire route
|
|
" drawRouteForLatLons(latlons);\n"
|
|
#endif
|
|
"}\n"
|
|
"\n"
|
|
"function drawRouteForLatLons(latlons) {\n"
|
|
|
|
// route will be drawn with these options
|
|
" var routeOptionsYellow = {\n"
|
|
" strokeColor: new Microsoft.Maps.Color(100, 255, 255, 0),\n"
|
|
" strokeThickness: 7,\n"
|
|
" strokeDashArray: '5 0',\n"
|
|
" zIndex: -2\n"
|
|
" };\n"
|
|
|
|
// create the route path
|
|
" var route = new Array();\n"
|
|
" var j=0;\n"
|
|
" while (j < latlons.length) { \n"
|
|
" route.push(new Microsoft.Maps.Location(latlons[j],latlons[j+1]));\n"
|
|
" j += 2;\n"
|
|
" }\n"
|
|
|
|
// create the route Polyline
|
|
" var routeYellow = new Microsoft.Maps.Polyline(route, routeOptionsYellow);\n"
|
|
" map.entities.push(routeYellow);\n"
|
|
|
|
"}\n"
|
|
|
|
"function drawIntervals() { \n"
|
|
// how many to draw?
|
|
#ifdef NOWEBKIT
|
|
" webBridge.intervalCount(drawIntervalsCount);\n"
|
|
#else
|
|
" drawIntervalsCount(webBridge.intervalCount());\n"
|
|
#endif
|
|
"}\n"
|
|
|
|
"function drawIntervalsCount(intervals) { \n"
|
|
|
|
// remove previous intervals highlighted
|
|
" j= intervalList.length;\n"
|
|
" while (j) {\n"
|
|
" var highlighted = intervalList.pop();\n"
|
|
" highlighted.setOptions( { visible: false });\n"
|
|
" j--;\n"
|
|
" }\n"
|
|
|
|
// how many to draw?
|
|
" while (intervals > 0) {\n"
|
|
#ifdef NOWEBKIT
|
|
" webBridge.getLatLons(intervals, drawInterval);\n"
|
|
#else
|
|
" drawInterval(webBridge.getLatLons(intervals));\n"
|
|
#endif
|
|
" intervals--;\n"
|
|
" }\n"
|
|
"}\n"
|
|
|
|
"function drawInterval(latlons) { \n"
|
|
// intervals will be drawn with these options
|
|
" var polyOptions = {\n"
|
|
" strokeColor: new Microsoft.Maps.Color(100, 0, 0, 255),\n"
|
|
" strokeThickness: 5,\n"
|
|
" strokeDashArray: '5 0',\n"
|
|
" zIndex: -1\n"
|
|
" };\n"
|
|
|
|
// create the route path
|
|
" var route = new Array();\n"
|
|
" var j=0;\n"
|
|
" while (j < latlons.length) { \n"
|
|
" route.push(new Microsoft.Maps.Location(latlons[j],latlons[j+1]));\n"
|
|
" j += 2;\n"
|
|
" }\n"
|
|
|
|
// create the route Polyline
|
|
" var intervalHighlighter = new Microsoft.Maps.Polyline(route, polyOptions);\n"
|
|
" map.entities.push(intervalHighlighter);\n"
|
|
" intervalList.push(intervalHighlighter);\n"
|
|
"}\n"
|
|
|
|
// initialise function called when map loaded
|
|
"function initialize() {\n"
|
|
|
|
// define initial map boundary
|
|
" var initialViewBounds = Microsoft.Maps.LocationRect.fromCorners("
|
|
"new Microsoft.Maps.Location(%1,%2), "
|
|
"new Microsoft.Maps.Location(%3,%4));\n"
|
|
|
|
// map options - we like AUTO change as you zoom in
|
|
" var options = {\n"
|
|
" credentials:\"AtgAt5wRsWGlmft5J_qUbo4rTJg_qZvepiwN_8FQMQwryIlMMCSpwCbvYiIZO5Zr\",\n" // API key, registered to GoldenCheetah
|
|
" bounds: initialViewBounds,\n"
|
|
" mapTypeId: Microsoft.Maps.MapTypeId.auto,\n"
|
|
" animate: true\n"
|
|
" };\n"
|
|
|
|
// setup the map, and fit to contain the limits of the route
|
|
" map = new Microsoft.Maps.Map(document.getElementById(\"map_canvas\"),options);\n"
|
|
|
|
// initialise local variables
|
|
" markerList = new Array();\n"
|
|
" intervalList = new Array();\n"
|
|
" polyList = new Array();\n"
|
|
|
|
// draw the main route data, getting the geo
|
|
// data from the webbridge - reduces data sent/received
|
|
// to the map server and makes the UI pretty snappy
|
|
" drawRoute();\n"
|
|
|
|
" drawIntervals();\n"
|
|
|
|
// catch signals to redraw intervals
|
|
" webBridge.drawIntervals.connect(drawIntervals);\n"
|
|
|
|
// we're done now let the C++ side draw its overlays
|
|
" webBridge.drawOverlays();\n"
|
|
|
|
"}\n"
|
|
"</script>\n").arg(minLat,0,'g',GPS_COORD_TO_STRING).arg(minLon,0,'g',GPS_COORD_TO_STRING).arg(maxLat,0,'g',GPS_COORD_TO_STRING).arg(maxLon,0,'g',GPS_COORD_TO_STRING);
|
|
|
|
// the main page is rather trivial
|
|
currentPage += QString("</head>\n"
|
|
"<body\">\n"
|
|
"<div id=\"map_canvas\"></div>\n"
|
|
"</body>\n"
|
|
"</html>\n");
|
|
}
|
|
|
|
|
|
QColor BingMap::GetColor(int watts)
|
|
{
|
|
if (range < 0) return Qt::red;
|
|
else return zoneColor(context->athlete->zones(myRideItem ? myRideItem->isRun : false)->whichZone(range, watts), 7);
|
|
}
|
|
|
|
// create the ride line
|
|
void
|
|
BingMap::drawShadedRoute()
|
|
{
|
|
int intervalTime = 60; // 60 seconds
|
|
double rtime=0; // running total for accumulated data
|
|
int count=0; // how many samples ?
|
|
int rwatts=0; // running total of watts
|
|
double prevtime=0; // time for previous point
|
|
|
|
QString code;
|
|
|
|
foreach(RideFilePoint *rfp, myRideItem->ride()->dataPoints()) {
|
|
|
|
if (count == 0) {
|
|
code = QString("{\nvar route = new Array();\n");
|
|
} else {
|
|
if (rfp->lat || rfp->lon)
|
|
code += QString("route.push(new Microsoft.Maps.Location(%1,%2));\n").arg(rfp->lat,0,'g',GPS_COORD_TO_STRING).arg(rfp->lon,0,'g',GPS_COORD_TO_STRING);
|
|
}
|
|
|
|
// running total of time
|
|
rtime += rfp->secs - prevtime;
|
|
rwatts += rfp->watts;
|
|
prevtime = rfp->secs;
|
|
count++;
|
|
|
|
// end of segment
|
|
if (rtime >= intervalTime) {
|
|
|
|
int avgWatts = rwatts / count;
|
|
QColor color = GetColor(avgWatts);
|
|
// thats this segment done, so finish off and
|
|
// add tooltip junk
|
|
count = rwatts = rtime = 0;
|
|
|
|
// color the polyline
|
|
code += QString(" var polyOptions = {\n"
|
|
" strokeColor: new Microsoft.Maps.Color(200, %1, %2, %3),\n"
|
|
" strokeThickness: 3,\n"
|
|
" strokeDashArray: '5 0',\n"
|
|
" zIndex: 1\n"
|
|
" };\n"
|
|
" var polyline = new Microsoft.Maps.Polyline(route, polyOptions);\n"
|
|
" polyline.setOptions(polyOptions);\n"
|
|
" map.entities.push(polyline);\n"
|
|
"}\n").arg(color.red())
|
|
.arg(color.green())
|
|
.arg(color.blue());
|
|
|
|
#ifdef NOWEBKIT
|
|
view->page()->runJavaScript(code);
|
|
#else
|
|
view->page()->mainFrame()->evaluateJavaScript(code);
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Static helper - havervine formula for calculating the distance
|
|
// between 2 geo co-ordinates
|
|
//
|
|
static const double DEG_TO_RAD = 0.017453292519943295769236907684886;
|
|
static const double EARTH_RADIUS_IN_METERS = 6372797.560856;
|
|
static double ArcInRadians(double fromLat, double fromLon, double toLat, double toLon) {
|
|
|
|
double latitudeArc = (fromLat - toLat) * DEG_TO_RAD;
|
|
double longitudeArc = (fromLon - toLon) * DEG_TO_RAD;
|
|
double latitudeH = sin(latitudeArc * 0.5);
|
|
latitudeH *= latitudeH;
|
|
double lontitudeH = sin(longitudeArc * 0.5);
|
|
lontitudeH *= lontitudeH;
|
|
double tmp = cos(fromLat*DEG_TO_RAD) * cos(toLat*DEG_TO_RAD);
|
|
return 2.0 * asin(sqrt(latitudeH + tmp*lontitudeH));
|
|
}
|
|
|
|
static double distanceBetween(double fromLat, double fromLon, double toLat, double toLon) {
|
|
return EARTH_RADIUS_IN_METERS*ArcInRadians(fromLat, fromLon, toLat, toLon);
|
|
}
|
|
|
|
void
|
|
BingMap::createMarkers()
|
|
{
|
|
QString code;
|
|
|
|
//
|
|
// START / FINISH MARKER
|
|
//
|
|
const QVector<RideFilePoint *> &points = myRideItem->ride()->dataPoints();
|
|
|
|
bool loop = distanceBetween(points[0]->lat,
|
|
points[0]->lon,
|
|
points[points.count()-1]->lat,
|
|
points[points.count()-1]->lon) < 100 ? true : false;
|
|
|
|
if (loop) {
|
|
|
|
code = QString("{ var latlng = new Microsoft.Maps.Location(%1,%2); "
|
|
"var pushpinOptions = { icon: 'qrc:images/maps/loop.png', height: 37, width: 32 };"
|
|
"var pushpin = new Microsoft.Maps.Pushpin(latlng, pushpinOptions);"
|
|
"map.entities.push(pushpin); }"
|
|
).arg(points[0]->lat,0,'g',GPS_COORD_TO_STRING).arg(points[0]->lon,0,'g',GPS_COORD_TO_STRING);
|
|
|
|
#ifdef NOWEBKIT
|
|
view->page()->runJavaScript(code);
|
|
#else
|
|
view->page()->mainFrame()->evaluateJavaScript(code);
|
|
#endif
|
|
|
|
} else {
|
|
// start / finish markers
|
|
code = QString("{ var latlng = new Microsoft.Maps.Location(%1,%2); "
|
|
"var pushpinOptions = { icon: 'qrc:images/maps/cycling.png', height: 37, width: 32 };"
|
|
"var pushpin = new Microsoft.Maps.Pushpin(latlng, pushpinOptions);"
|
|
"map.entities.push(pushpin); }"
|
|
).arg(points[0]->lat,0,'g',GPS_COORD_TO_STRING).arg(points[0]->lon,0,'g',GPS_COORD_TO_STRING);
|
|
|
|
#ifdef NOWEBKIT
|
|
view->page()->runJavaScript(code);
|
|
#else
|
|
view->page()->mainFrame()->evaluateJavaScript(code);
|
|
#endif
|
|
|
|
code = QString("{ var latlng = new Microsoft.Maps.Location(%1,%2); "
|
|
"var pushpinOptions = { icon: 'qrc:images/maps/finish.png', height: 37, width: 32 };"
|
|
"var pushpin = new Microsoft.Maps.Pushpin(latlng, pushpinOptions);"
|
|
"map.entities.push(pushpin); }"
|
|
).arg(points[points.count()-1]->lat,0,'g',GPS_COORD_TO_STRING).arg(points[points.count()-1]->lon,0,'g',GPS_COORD_TO_STRING);
|
|
|
|
#ifdef NOWEBKIT
|
|
view->page()->runJavaScript(code);
|
|
#else
|
|
view->page()->mainFrame()->evaluateJavaScript(code);
|
|
#endif
|
|
}
|
|
|
|
//
|
|
// STOPS - BEER AND BURRITO TIME (> 5 mins in same spot)
|
|
//
|
|
double stoplat=0, stoplon=0;
|
|
double laststoptime=0;
|
|
double lastlat=0, lastlon=0;
|
|
int stoptime=0;
|
|
static const int BEERANDBURRITO = 300; // anything longer than 5 minutes
|
|
|
|
foreach(RideFilePoint *rfp, myRideItem->ride()->dataPoints())
|
|
{
|
|
if (!rfp->lat || !rfp->lon) continue; // ignore blank values
|
|
|
|
if (!stoplat || !stoplon) { // register first gps co-ord
|
|
stoplat = rfp->lat;
|
|
stoplon = rfp->lon;
|
|
laststoptime = rfp->secs;
|
|
}
|
|
|
|
if (distanceBetween(rfp->lat, rfp->lon, stoplat, stoplon) < 20) {
|
|
if (rfp->secs - laststoptime > myRideItem->ride()->recIntSecs()) stoptime += rfp->secs - laststoptime;
|
|
else stoptime += myRideItem->ride()->recIntSecs();
|
|
} else if (rfp->secs - laststoptime > myRideItem->ride()->recIntSecs()) {
|
|
stoptime += rfp->secs - laststoptime;
|
|
stoplat = rfp->lat;
|
|
stoplon = rfp->lon;
|
|
} else {
|
|
stoptime = 0;
|
|
stoplat = rfp->lat;
|
|
stoplon = rfp->lon;
|
|
}
|
|
|
|
if (stoptime > BEERANDBURRITO) { // 3 minutes is more than a traffic light stop dude.
|
|
|
|
if ((!lastlat && !lastlon) || distanceBetween(lastlat, lastlon, stoplat, stoplon)>100) {
|
|
lastlat = stoplat;
|
|
lastlon = stoplon;
|
|
code = QString("{ var latlng = new Microsoft.Maps.Location(%1,%2); "
|
|
"var pushpinOptions = { icon: 'qrc:images/maps/cycling_feed.png', height: 37, width: 32 };"
|
|
"var pushpin = new Microsoft.Maps.Pushpin(latlng, pushpinOptions);"
|
|
"map.entities.push(pushpin); }"
|
|
).arg(rfp->lat,0,'g',GPS_COORD_TO_STRING).arg(rfp->lon,0,'g',GPS_COORD_TO_STRING);
|
|
|
|
#ifdef NOWEBKIT
|
|
view->page()->runJavaScript(code);
|
|
#else
|
|
view->page()->mainFrame()->evaluateJavaScript(code);
|
|
#endif
|
|
stoptime=0;
|
|
}
|
|
stoplat=stoplon=stoptime=0;
|
|
}
|
|
laststoptime = rfp->secs;
|
|
}
|
|
|
|
//
|
|
// INTERVAL MARKERS
|
|
//
|
|
if (myRideItem->intervals().count() == 0) return;
|
|
|
|
int interval=0;
|
|
foreach (IntervalItem *x, myRideItem->intervals()) {
|
|
|
|
int offset = myRideItem->ride()->intervalBeginSecs(x->start);
|
|
code = QString("{ var latlng = new Microsoft.Maps.Location(%1,%2);\n"
|
|
"var pushpinOptions = { };\n"
|
|
"var pushpin = new Microsoft.Maps.Pushpin(latlng, pushpinOptions);\n"
|
|
"map.entities.push(pushpin);\n"
|
|
"if (pushpin.cm1001_er_etr) pushpin.cm1001_er_etr.dom.setAttribute('title', '%3');\n"
|
|
"if (pushpin.cm1002_er_etr) pushpin.cm1002_er_etr.dom.setAttribute('title', '%3');\n"
|
|
"pushpinClick= Microsoft.Maps.Events.addHandler(pushpin, 'click', function(event) { webBridge.toggleInterval(%4); });\n"
|
|
" }")
|
|
.arg(myRideItem->ride()->dataPoints()[offset]->lat,0,'g',GPS_COORD_TO_STRING)
|
|
.arg(myRideItem->ride()->dataPoints()[offset]->lon,0,'g',GPS_COORD_TO_STRING)
|
|
.arg(x->name)
|
|
.arg(interval);
|
|
|
|
#ifdef NOWEBKIT
|
|
view->page()->runJavaScript(code);
|
|
#else
|
|
view->page()->mainFrame()->evaluateJavaScript(code);
|
|
#endif
|
|
interval++;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
void BingMap::zoomInterval(IntervalItem *which)
|
|
{
|
|
RideItem *ride = myRideItem;
|
|
|
|
// null ride
|
|
if (!ride || !ride->ride()) return;
|
|
|
|
int start = ride->ride()->timeIndex(which->start);
|
|
int end = ride->ride()->timeIndex(which->stop);
|
|
|
|
// out of bounds
|
|
if (start < 0 || start > ride->ride()->dataPoints().count()-1 ||
|
|
end < 0 || end > ride->ride()->dataPoints().count()-1) return;
|
|
|
|
// Get the bounding rectangle for this interval
|
|
double minLat, minLon, maxLat, maxLon;
|
|
minLat = minLon = 1000;
|
|
maxLat = maxLon = -1000; // larger than 360
|
|
|
|
// get bounding co-ordinates for ride
|
|
for(int i=start; i<= end; i++) {
|
|
RideFilePoint *rfp = ride->ride()->dataPoints().at(i);
|
|
if (rfp->lat || rfp->lon) {
|
|
minLat = std::min(minLat,rfp->lat);
|
|
maxLat = std::max(maxLat,rfp->lat);
|
|
minLon = std::min(minLon,rfp->lon);
|
|
maxLon = std::max(maxLon,rfp->lon);
|
|
}
|
|
}
|
|
|
|
// now zoom to interval
|
|
QString code = QString("{\n"
|
|
" var viewBounds = Microsoft.Maps.LocationRect.fromCorners("
|
|
" new Microsoft.Maps.Location(%1,%2), "
|
|
" new Microsoft.Maps.Location(%3,%4));\n"
|
|
" map.setView( { bounds: viewBounds });\n }")
|
|
.arg(minLat,0,'g',GPS_COORD_TO_STRING)
|
|
.arg(minLon,0,'g',GPS_COORD_TO_STRING)
|
|
.arg(maxLat,0,'g',GPS_COORD_TO_STRING)
|
|
.arg(maxLon,0,'g',GPS_COORD_TO_STRING);
|
|
|
|
#ifdef NOWEBKIT
|
|
view->page()->runJavaScript(code);
|
|
#else
|
|
view->page()->mainFrame()->evaluateJavaScript(code);
|
|
#endif
|
|
}
|
|
|
|
// quick diag, used to debug code only
|
|
void BWebBridge::call(int count) { qDebug()<<"webBridge call:"<<count; }
|
|
|
|
// how many intervals are highlighted?
|
|
int
|
|
BWebBridge::intervalCount()
|
|
{
|
|
RideItem *rideItem = gm->property("ride").value<RideItem*>();
|
|
if (rideItem)
|
|
return rideItem->intervalsSelected().count();
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
// get a latlon array for the i'th selected interval
|
|
QVariantList
|
|
BWebBridge::getLatLons(int i)
|
|
{
|
|
QVariantList latlons;
|
|
RideItem *rideItem = gm->property("ride").value<RideItem*>();
|
|
|
|
if (rideItem == NULL) return latlons;
|
|
|
|
// valid highlighted interval ?
|
|
if (rideItem && i > 0 && rideItem->intervalsSelected().count() >= i) {
|
|
|
|
IntervalItem *current = rideItem->intervalsSelected().at(i-1);
|
|
|
|
// so this one is the interval we need.. lets
|
|
// snaffle up the points in this section
|
|
foreach (RideFilePoint *p1, rideItem->ride()->dataPoints()) {
|
|
if (p1->secs+rideItem->ride()->recIntSecs() > current->start
|
|
&& p1->secs< current->stop) {
|
|
|
|
if (p1->lat || p1->lon) {
|
|
latlons << p1->lat;
|
|
latlons << p1->lon;
|
|
}
|
|
}
|
|
}
|
|
return latlons;
|
|
|
|
} else {
|
|
|
|
// get latlons for entire route
|
|
foreach (RideFilePoint *p1, rideItem->ride()->dataPoints()) {
|
|
if (p1->lat || p1->lon) {
|
|
latlons << p1->lat;
|
|
latlons << p1->lon;
|
|
}
|
|
}
|
|
}
|
|
return latlons;
|
|
}
|
|
|
|
// once the basic map and route have been marked, overlay markers, shaded areas etc
|
|
void
|
|
BWebBridge::drawOverlays()
|
|
{
|
|
// overlay the markers
|
|
gm->createMarkers();
|
|
|
|
// overlay a shaded route
|
|
gm->drawShadedRoute();
|
|
}
|
|
|
|
// interval marker was clicked on the map, toggle its display
|
|
void
|
|
BWebBridge::toggleInterval(int x)
|
|
{
|
|
RideItem *rideItem = gm->property("ride").value<RideItem*>();
|
|
if (x < 0 || rideItem->intervals().count() <= x) return;
|
|
|
|
IntervalItem *current = rideItem->intervals().at(x);
|
|
if (current) {
|
|
current->selected = !current->selected;
|
|
context->notifyIntervalItemSelectionChanged(current);
|
|
}
|
|
}
|