SlideShare une entreprise Scribd logo
1  sur  23
Django GeoTracker
About me
• My name is Jani Tiainen
• I work for company called Keypro
• Django and GeoDjango user since 2008
• Individual member of DSF
• jtiai on #django IRC channel
What we will build?
• A web site that takes advantage of geolocation service.
• A responsive layout with map and few buttons to record GPS location
from mobile device.
• A tool to convert individual tracked points to a linestring.
• A tool that can show linestring on a map.
App skeleton
• Can be cloned with git from https://github.com/jtiai/dceu-geotracker
• Contains base layout.
• Index page with locate-button.
• Used to verify that all essential parts do work.
Geolocation service
• Can query geolocation from browser. Most usable with mobile
devices with GPS.
• Works pretty well.
• Lately browsers have been requiring SSL (HTTPS) when using
geolocation.
• Serveo or ngrok can solve problem in small scale testing and development
First step
• Let’s make sure that everyone has skeleton app up and running and
can locate themselves on a map using locate-button.
• Make sure that migrations are done.
• Make sure that superuser is created to access admin ui.
• There are bugs in instructions. 
• docker-compose run –rm web pipenv run python manage.py migrate
• docker-compose run –rm web pipenv run python manage.py
createsuperuser
• And a bug in the code…. 
• start.sh script had CRLF-line-endings. Not good for *nix. 
Record location
• Let’s add a new button to store location to database.
• Each ”track” has it’s own (unique) name.
Index.html
<div class="form-group">
<label for="tracking_name">Tracking name</label>
<input type="text" maxlength="200" id="tracking_name" class="form-control" name="name" placeholder="Name of the tracking" required>
</div>
<button type="button" class="btn btn-primary" onclick="single_locate()">Locate</button>
<button type="button" class="btn btn-primary" onclick="start_tracking()">Start tracking</button>
Record location JS
let watchId = null;
let currentLocationMarker = null;
let trackingMarkers = new Array(5);
let trackingMarkerIndex = 0; // current counter
function start_tracking() {
if (watchId) {
alert("You're already tracking. Stop it first to restart");
} else {
let tracking_name = document.getElementsByName('name')[0];
if (!tracking_name.value) {
alert("Please set tracking name first.");
return;
}
tracking_name.disabled = true;
watchId = navigator.geolocation.watchPosition(function (position) {
console.log("Tracked new position", position);
if (trackingMarkers[trackingMarkerIndex]) {
// Remove oldest markeer
myMap.removeLayer(trackingMarkers[trackingMarkerIndex]);
}
trackingMarkers[trackingMarkerIndex] = L.marker([position.coords.latitude, position.coords.longitude], {icon: violetIcon});
trackingMarkers[trackingMarkerIndex].addTo(myMap);
trackingMarkerIndex++;
if (trackingMarkerIndex >= trackingMarkers.length) {
trackingMarkerIndex = 0; // Rollover
}
let xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function () {
// Handle error, in case of successful we don't care
};
xhttp.open("POST", tracking_point_url);
xhttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
let data = new URLSearchParams();
data.append('name', tracking_name.value);
data.append('timestamp', position.timestamp.toString());
data.append('altitude', position.coords.altitude == null ? "" : position.coords.altitude.toString());
data.append('altitude_accuracy', position.coords.altitudeAccuracy == null ? "" : position.coords.altitudeAccuracy.toString());
data.append('accuracy', position.coords.accuracy.toString());
data.append('latitude', position.coords.latitude.toString());
data.append('longitude', position.coords.longitude.toString());
xhttp.send(data);
}, null, {timeout: 5000, enableHighAccuracy: true});
}
}
Record location view code
@method_decorator(csrf_exempt, name="dispatch")
class TrackingPointAPIView(View):
def post(self, request):
form = TrackingPointForm(request.POST)
if form.is_valid():
tp = TrackedPoint()
# Timestamp is in milliseconds
tp.name = form.cleaned_data["name"]
tp.timestamp = datetime.datetime.fromtimestamp(
form.cleaned_data["timestamp"] / 1000
)
tp.location = Point(
form.cleaned_data["longitude"], form.cleaned_data["latitude"]
)
tp.save()
return JsonResponse({"successful": True})
return JsonResponse({"succesful": False, "errors": form.errors})
Don’t forget to add required imports…
Record location url config
And let’s test we everything works as expected…
urlpatterns = [
path('admin/', admin.site.urls),
path('tracking-point/', views.TrackingPointAPIView.as_view(), name='tracking-point-api’),
path('', views.IndexView.as_view(), name='tracking-index'),
]
Testing and verifying
• Run devserver
• docker-compose up
• Route https to local machine (serveo or ngrok)
• Note with serveo – it doesn’t redirect http to https.
• Open index page and store point(s) to database.
Make tracking to stop
• Add stop-tracking button
<button type="button" class="btn btn-primary" onclick="stop_tracking()">Stop tracking</button>
function stop_tracking() {
if (!watchId) {
alert("You're not tracking yet. Start tracking first.");
} else {
navigator.geolocation.clearWatch(watchId);
watchId = null;
let tracking_name = document.getElementsByName('name')[0];
tracking_name.disabled = false;
}
}
List tracked points
• Simple list with name of tracking and number of points in it.
• A button to convert points to a linestring.
{% extends "geotracker/base.html" %}
{% block contents %}
<form action="{% url "route-create" %}" method="post">
{% csrf_token %}
<table class="table">
<thead>
<tr>
<th scope="col">Tracking name</th>
<th scope="col">Number points</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
{% for r in track_names %}
<tr>
<td>{{ r.name }}</td>
<td>{{ r.num_points }}</td>
<td><button class="btn btn-primary btn-sm" name="name" value="{{ r.name }}">Create Track</button></td>
</tr>
{% endfor %}
</tbody>
</table>
</form>
{% endblock %}
List tracked points, the view
class TrackingPointsListView(View):
def get(self, request):
track_names = (
TrackedPoint.objects.values("name")
.distinct()
.annotate(num_points=Count("name"))
.values("name", "num_points")
)
return render(
request,
"geotracker/tracked_points_list.html",
{"track_names": track_names, "tracked_page": " active"},
)
path('tracking-points-list/', views.TrackingPointsListView.as_view(), name='tracking-points-list'),
A view to create a linestring
class RouteCreateView(View):
def post(self, request):
name = request.POST["name"]
qs = TrackedPoint.objects.filter(name=name)
# Create line
points = [tp.location for tp in qs]
linestring = LineString(points)
RouteLine.objects.create(name=name, location=linestring)
return redirect(reverse("routes-list"))
path('route-create/', views.RouteCreateView.as_view(), name='route-create'),
Add page to list lines
• Add a list and show on map button
{% extends "geotracker/base.html" %}
{% load staticfiles %}
{% block extra_head %}
<script src="{% static "js/geotracker.js" %}"></script>
<style>
#map {
height: 350px;
margin-top: 16px;
margin-bottom: 16px;
}
</style>
{% endblock %}
{% block onloadcall %}line_startup();{% endblock %}
{% block contents %}
<div id="map"></div>
<table class="table">
<thead>
<tr>
<th scope="col">Tracking name</th>
<th scope="col">Length (m)</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
{% for r in lines %}
<tr>
<td>{{ r.name }}</td>
<td>{{ r.route_length|floatformat:2 }}</td>
<td><button class="btn btn-primary btn-sm" onclick="show_line_on_map({{ r.get_geojson_feature }})">Show on map</button></td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}
…template continued…
Add a view to provide list of linestrings
class RoutesListView(View):
def get(self, request):
lines = RouteLine.objects.all()
return render(
request,
"geotracker/tracked_routes.html",
{"lines": lines, "tracked_lines_page": " active"},
)
path('routes/', views.RoutesListView.as_view(), name="routes-list"),
Aaaand… Does it work?
• If it does, good.
• If not, let’s try to figure out what’s wrong.
Working with geometries
• Distance calculations
• Length calculations
• Proximity queries
Smoothing GPS data
• We’re not going to do that… 
• GPS data jumps around due inaccuracies.
• Smoothing algorithms do exist.
• Based on predictions of next point and statistics of previous points.
• Quite common solution is to use is Kalman filter (or some derivation
from that).
Questions, comments?

Contenu connexe

Tendances

JQuery In Rails
JQuery In RailsJQuery In Rails
JQuery In Rails
Louie Zhao
 
Курсы по мобильной разработке под iOS. 4 лекция. Возможности телефона
Курсы по мобильной разработке под iOS. 4 лекция. Возможности телефонаКурсы по мобильной разработке под iOS. 4 лекция. Возможности телефона
Курсы по мобильной разработке под iOS. 4 лекция. Возможности телефона
Глеб Тарасов
 

Tendances (15)

Building Persona: federated and privacy-sensitive identity for the Web (LCA 2...
Building Persona: federated and privacy-sensitive identity for the Web (LCA 2...Building Persona: federated and privacy-sensitive identity for the Web (LCA 2...
Building Persona: federated and privacy-sensitive identity for the Web (LCA 2...
 
Beyond the DOM: Sane Structure for JS Apps
Beyond the DOM: Sane Structure for JS AppsBeyond the DOM: Sane Structure for JS Apps
Beyond the DOM: Sane Structure for JS Apps
 
JQuery In Rails
JQuery In RailsJQuery In Rails
JQuery In Rails
 
Lenses
LensesLenses
Lenses
 
course js day 3
course js day 3course js day 3
course js day 3
 
jQuery Data Manipulate API - A source code dissecting journey
jQuery Data Manipulate API - A source code dissecting journeyjQuery Data Manipulate API - A source code dissecting journey
jQuery Data Manipulate API - A source code dissecting journey
 
Курсы по мобильной разработке под iOS. 4 лекция. Возможности телефона
Курсы по мобильной разработке под iOS. 4 лекция. Возможности телефонаКурсы по мобильной разработке под iOS. 4 лекция. Возможности телефона
Курсы по мобильной разработке под iOS. 4 лекция. Возможности телефона
 
How to Bring Common UI Patterns to ADF
How to Bring Common UI Patterns to ADF How to Bring Common UI Patterns to ADF
How to Bring Common UI Patterns to ADF
 
The web beyond "usernames & passwords"
The web beyond "usernames & passwords"The web beyond "usernames & passwords"
The web beyond "usernames & passwords"
 
Android Animation (in tamil)
Android Animation (in tamil)Android Animation (in tamil)
Android Animation (in tamil)
 
Functionality Focused Code Organization
Functionality Focused Code OrganizationFunctionality Focused Code Organization
Functionality Focused Code Organization
 
How te bring common UI patterns to ADF
How te bring common UI patterns to ADFHow te bring common UI patterns to ADF
How te bring common UI patterns to ADF
 
Sequence diagrams
Sequence diagramsSequence diagrams
Sequence diagrams
 
Eddystone Beacons - Physical Web - Giving a URL to All Objects
Eddystone Beacons - Physical Web - Giving a URL to All ObjectsEddystone Beacons - Physical Web - Giving a URL to All Objects
Eddystone Beacons - Physical Web - Giving a URL to All Objects
 
3D Touch: Preparando sua app para o futuro do iOS
3D Touch: Preparando sua app para o futuro do iOS3D Touch: Preparando sua app para o futuro do iOS
3D Touch: Preparando sua app para o futuro do iOS
 

Similaire à Django GeoTracker

How Quick Can We Be? Data Visualization Techniques for Engineers.
How Quick Can We Be? Data Visualization Techniques for Engineers. How Quick Can We Be? Data Visualization Techniques for Engineers.
How Quick Can We Be? Data Visualization Techniques for Engineers.
Avni Khatri
 
Big Data for each one of us
Big Data for each one of usBig Data for each one of us
Big Data for each one of us
OSCON Byrum
 
Html and i_phone_mobile-2
Html and i_phone_mobile-2Html and i_phone_mobile-2
Html and i_phone_mobile-2
tonvanbart
 
Sapo GIS Hands-On
Sapo GIS Hands-OnSapo GIS Hands-On
Sapo GIS Hands-On
codebits
 
Gis SAPO Hands On
Gis SAPO Hands OnGis SAPO Hands On
Gis SAPO Hands On
codebits
 
Designing and developing mobile web applications with Mockup, Sencha Touch an...
Designing and developing mobile web applications with Mockup, Sencha Touch an...Designing and developing mobile web applications with Mockup, Sencha Touch an...
Designing and developing mobile web applications with Mockup, Sencha Touch an...
Matteo Collina
 
Vidéo approche en immobilier
Vidéo approche en immobilierVidéo approche en immobilier
Vidéo approche en immobilier
hervepouliot
 
Android Best Practices
Android Best PracticesAndroid Best Practices
Android Best Practices
Yekmer Simsek
 
#NewMeetup Performance
#NewMeetup Performance#NewMeetup Performance
#NewMeetup Performance
Justin Cataldo
 

Similaire à Django GeoTracker (20)

@Anywhere
@Anywhere@Anywhere
@Anywhere
 
How Quick Can We Be? Data Visualization Techniques for Engineers.
How Quick Can We Be? Data Visualization Techniques for Engineers. How Quick Can We Be? Data Visualization Techniques for Engineers.
How Quick Can We Be? Data Visualization Techniques for Engineers.
 
Angular Workshop_Sarajevo2
Angular Workshop_Sarajevo2Angular Workshop_Sarajevo2
Angular Workshop_Sarajevo2
 
Clean Javascript
Clean JavascriptClean Javascript
Clean Javascript
 
How data rules the world: Telemetry in Battlefield Heroes
How data rules the world: Telemetry in Battlefield HeroesHow data rules the world: Telemetry in Battlefield Heroes
How data rules the world: Telemetry in Battlefield Heroes
 
Big Data for each one of us
Big Data for each one of usBig Data for each one of us
Big Data for each one of us
 
Html and i_phone_mobile-2
Html and i_phone_mobile-2Html and i_phone_mobile-2
Html and i_phone_mobile-2
 
Sapo GIS Hands-On
Sapo GIS Hands-OnSapo GIS Hands-On
Sapo GIS Hands-On
 
Gis SAPO Hands On
Gis SAPO Hands OnGis SAPO Hands On
Gis SAPO Hands On
 
Geo django
Geo djangoGeo django
Geo django
 
Designing and developing mobile web applications with Mockup, Sencha Touch an...
Designing and developing mobile web applications with Mockup, Sencha Touch an...Designing and developing mobile web applications with Mockup, Sencha Touch an...
Designing and developing mobile web applications with Mockup, Sencha Touch an...
 
Building Real Time Systems on MongoDB Using the Oplog at Stripe
Building Real Time Systems on MongoDB Using the Oplog at StripeBuilding Real Time Systems on MongoDB Using the Oplog at Stripe
Building Real Time Systems on MongoDB Using the Oplog at Stripe
 
Vidéo approche en immobilier
Vidéo approche en immobilierVidéo approche en immobilier
Vidéo approche en immobilier
 
Geolocation and Mapping
Geolocation and MappingGeolocation and Mapping
Geolocation and Mapping
 
mobl
moblmobl
mobl
 
JavaScript Refactoring
JavaScript RefactoringJavaScript Refactoring
JavaScript Refactoring
 
Android Best Practices
Android Best PracticesAndroid Best Practices
Android Best Practices
 
#NewMeetup Performance
#NewMeetup Performance#NewMeetup Performance
#NewMeetup Performance
 
Baitap tkw
Baitap tkwBaitap tkw
Baitap tkw
 
Seti 09
Seti 09Seti 09
Seti 09
 

Dernier

%+27788225528 love spells in Toronto Psychic Readings, Attraction spells,Brin...
%+27788225528 love spells in Toronto Psychic Readings, Attraction spells,Brin...%+27788225528 love spells in Toronto Psychic Readings, Attraction spells,Brin...
%+27788225528 love spells in Toronto Psychic Readings, Attraction spells,Brin...
masabamasaba
 
%+27788225528 love spells in new york Psychic Readings, Attraction spells,Bri...
%+27788225528 love spells in new york Psychic Readings, Attraction spells,Bri...%+27788225528 love spells in new york Psychic Readings, Attraction spells,Bri...
%+27788225528 love spells in new york Psychic Readings, Attraction spells,Bri...
masabamasaba
 
%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...
%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...
%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...
masabamasaba
 
%+27788225528 love spells in Colorado Springs Psychic Readings, Attraction sp...
%+27788225528 love spells in Colorado Springs Psychic Readings, Attraction sp...%+27788225528 love spells in Colorado Springs Psychic Readings, Attraction sp...
%+27788225528 love spells in Colorado Springs Psychic Readings, Attraction sp...
masabamasaba
 
Abortion Pills In Pretoria ](+27832195400*)[ 🏥 Women's Abortion Clinic In Pre...
Abortion Pills In Pretoria ](+27832195400*)[ 🏥 Women's Abortion Clinic In Pre...Abortion Pills In Pretoria ](+27832195400*)[ 🏥 Women's Abortion Clinic In Pre...
Abortion Pills In Pretoria ](+27832195400*)[ 🏥 Women's Abortion Clinic In Pre...
Medical / Health Care (+971588192166) Mifepristone and Misoprostol tablets 200mg
 
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
VictoriaMetrics
 

Dernier (20)

Direct Style Effect Systems - The Print[A] Example - A Comprehension Aid
Direct Style Effect Systems -The Print[A] Example- A Comprehension AidDirect Style Effect Systems -The Print[A] Example- A Comprehension Aid
Direct Style Effect Systems - The Print[A] Example - A Comprehension Aid
 
%in Bahrain+277-882-255-28 abortion pills for sale in Bahrain
%in Bahrain+277-882-255-28 abortion pills for sale in Bahrain%in Bahrain+277-882-255-28 abortion pills for sale in Bahrain
%in Bahrain+277-882-255-28 abortion pills for sale in Bahrain
 
VTU technical seminar 8Th Sem on Scikit-learn
VTU technical seminar 8Th Sem on Scikit-learnVTU technical seminar 8Th Sem on Scikit-learn
VTU technical seminar 8Th Sem on Scikit-learn
 
%in Rustenburg+277-882-255-28 abortion pills for sale in Rustenburg
%in Rustenburg+277-882-255-28 abortion pills for sale in Rustenburg%in Rustenburg+277-882-255-28 abortion pills for sale in Rustenburg
%in Rustenburg+277-882-255-28 abortion pills for sale in Rustenburg
 
%in Benoni+277-882-255-28 abortion pills for sale in Benoni
%in Benoni+277-882-255-28 abortion pills for sale in Benoni%in Benoni+277-882-255-28 abortion pills for sale in Benoni
%in Benoni+277-882-255-28 abortion pills for sale in Benoni
 
WSO2Con2024 - Enabling Transactional System's Exponential Growth With Simplicity
WSO2Con2024 - Enabling Transactional System's Exponential Growth With SimplicityWSO2Con2024 - Enabling Transactional System's Exponential Growth With Simplicity
WSO2Con2024 - Enabling Transactional System's Exponential Growth With Simplicity
 
Artyushina_Guest lecture_YorkU CS May 2024.pptx
Artyushina_Guest lecture_YorkU CS May 2024.pptxArtyushina_Guest lecture_YorkU CS May 2024.pptx
Artyushina_Guest lecture_YorkU CS May 2024.pptx
 
%+27788225528 love spells in Toronto Psychic Readings, Attraction spells,Brin...
%+27788225528 love spells in Toronto Psychic Readings, Attraction spells,Brin...%+27788225528 love spells in Toronto Psychic Readings, Attraction spells,Brin...
%+27788225528 love spells in Toronto Psychic Readings, Attraction spells,Brin...
 
WSO2CON2024 - It's time to go Platformless
WSO2CON2024 - It's time to go PlatformlessWSO2CON2024 - It's time to go Platformless
WSO2CON2024 - It's time to go Platformless
 
%+27788225528 love spells in new york Psychic Readings, Attraction spells,Bri...
%+27788225528 love spells in new york Psychic Readings, Attraction spells,Bri...%+27788225528 love spells in new york Psychic Readings, Attraction spells,Bri...
%+27788225528 love spells in new york Psychic Readings, Attraction spells,Bri...
 
WSO2CON 2024 Slides - Open Source to SaaS
WSO2CON 2024 Slides - Open Source to SaaSWSO2CON 2024 Slides - Open Source to SaaS
WSO2CON 2024 Slides - Open Source to SaaS
 
WSO2CON 2024 - Navigating API Complexity: REST, GraphQL, gRPC, Websocket, Web...
WSO2CON 2024 - Navigating API Complexity: REST, GraphQL, gRPC, Websocket, Web...WSO2CON 2024 - Navigating API Complexity: REST, GraphQL, gRPC, Websocket, Web...
WSO2CON 2024 - Navigating API Complexity: REST, GraphQL, gRPC, Websocket, Web...
 
%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...
%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...
%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...
 
WSO2CON 2024 - API Management Usage at La Poste and Its Impact on Business an...
WSO2CON 2024 - API Management Usage at La Poste and Its Impact on Business an...WSO2CON 2024 - API Management Usage at La Poste and Its Impact on Business an...
WSO2CON 2024 - API Management Usage at La Poste and Its Impact on Business an...
 
What Goes Wrong with Language Definitions and How to Improve the Situation
What Goes Wrong with Language Definitions and How to Improve the SituationWhat Goes Wrong with Language Definitions and How to Improve the Situation
What Goes Wrong with Language Definitions and How to Improve the Situation
 
%+27788225528 love spells in Colorado Springs Psychic Readings, Attraction sp...
%+27788225528 love spells in Colorado Springs Psychic Readings, Attraction sp...%+27788225528 love spells in Colorado Springs Psychic Readings, Attraction sp...
%+27788225528 love spells in Colorado Springs Psychic Readings, Attraction sp...
 
Abortion Pills In Pretoria ](+27832195400*)[ 🏥 Women's Abortion Clinic In Pre...
Abortion Pills In Pretoria ](+27832195400*)[ 🏥 Women's Abortion Clinic In Pre...Abortion Pills In Pretoria ](+27832195400*)[ 🏥 Women's Abortion Clinic In Pre...
Abortion Pills In Pretoria ](+27832195400*)[ 🏥 Women's Abortion Clinic In Pre...
 
%in tembisa+277-882-255-28 abortion pills for sale in tembisa
%in tembisa+277-882-255-28 abortion pills for sale in tembisa%in tembisa+277-882-255-28 abortion pills for sale in tembisa
%in tembisa+277-882-255-28 abortion pills for sale in tembisa
 
WSO2CON 2024 - Does Open Source Still Matter?
WSO2CON 2024 - Does Open Source Still Matter?WSO2CON 2024 - Does Open Source Still Matter?
WSO2CON 2024 - Does Open Source Still Matter?
 
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
 

Django GeoTracker

  • 2. About me • My name is Jani Tiainen • I work for company called Keypro • Django and GeoDjango user since 2008 • Individual member of DSF • jtiai on #django IRC channel
  • 3. What we will build? • A web site that takes advantage of geolocation service. • A responsive layout with map and few buttons to record GPS location from mobile device. • A tool to convert individual tracked points to a linestring. • A tool that can show linestring on a map.
  • 4. App skeleton • Can be cloned with git from https://github.com/jtiai/dceu-geotracker • Contains base layout. • Index page with locate-button. • Used to verify that all essential parts do work.
  • 5. Geolocation service • Can query geolocation from browser. Most usable with mobile devices with GPS. • Works pretty well. • Lately browsers have been requiring SSL (HTTPS) when using geolocation. • Serveo or ngrok can solve problem in small scale testing and development
  • 6. First step • Let’s make sure that everyone has skeleton app up and running and can locate themselves on a map using locate-button. • Make sure that migrations are done. • Make sure that superuser is created to access admin ui. • There are bugs in instructions.  • docker-compose run –rm web pipenv run python manage.py migrate • docker-compose run –rm web pipenv run python manage.py createsuperuser • And a bug in the code….  • start.sh script had CRLF-line-endings. Not good for *nix. 
  • 7. Record location • Let’s add a new button to store location to database. • Each ”track” has it’s own (unique) name. Index.html <div class="form-group"> <label for="tracking_name">Tracking name</label> <input type="text" maxlength="200" id="tracking_name" class="form-control" name="name" placeholder="Name of the tracking" required> </div> <button type="button" class="btn btn-primary" onclick="single_locate()">Locate</button> <button type="button" class="btn btn-primary" onclick="start_tracking()">Start tracking</button>
  • 8. Record location JS let watchId = null; let currentLocationMarker = null; let trackingMarkers = new Array(5); let trackingMarkerIndex = 0; // current counter
  • 9. function start_tracking() { if (watchId) { alert("You're already tracking. Stop it first to restart"); } else { let tracking_name = document.getElementsByName('name')[0]; if (!tracking_name.value) { alert("Please set tracking name first."); return; } tracking_name.disabled = true; watchId = navigator.geolocation.watchPosition(function (position) { console.log("Tracked new position", position); if (trackingMarkers[trackingMarkerIndex]) { // Remove oldest markeer myMap.removeLayer(trackingMarkers[trackingMarkerIndex]); } trackingMarkers[trackingMarkerIndex] = L.marker([position.coords.latitude, position.coords.longitude], {icon: violetIcon}); trackingMarkers[trackingMarkerIndex].addTo(myMap); trackingMarkerIndex++; if (trackingMarkerIndex >= trackingMarkers.length) { trackingMarkerIndex = 0; // Rollover } let xhttp = new XMLHttpRequest(); xhttp.onreadystatechange = function () { // Handle error, in case of successful we don't care }; xhttp.open("POST", tracking_point_url); xhttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); let data = new URLSearchParams(); data.append('name', tracking_name.value); data.append('timestamp', position.timestamp.toString()); data.append('altitude', position.coords.altitude == null ? "" : position.coords.altitude.toString()); data.append('altitude_accuracy', position.coords.altitudeAccuracy == null ? "" : position.coords.altitudeAccuracy.toString()); data.append('accuracy', position.coords.accuracy.toString()); data.append('latitude', position.coords.latitude.toString()); data.append('longitude', position.coords.longitude.toString()); xhttp.send(data); }, null, {timeout: 5000, enableHighAccuracy: true}); } }
  • 10. Record location view code @method_decorator(csrf_exempt, name="dispatch") class TrackingPointAPIView(View): def post(self, request): form = TrackingPointForm(request.POST) if form.is_valid(): tp = TrackedPoint() # Timestamp is in milliseconds tp.name = form.cleaned_data["name"] tp.timestamp = datetime.datetime.fromtimestamp( form.cleaned_data["timestamp"] / 1000 ) tp.location = Point( form.cleaned_data["longitude"], form.cleaned_data["latitude"] ) tp.save() return JsonResponse({"successful": True}) return JsonResponse({"succesful": False, "errors": form.errors}) Don’t forget to add required imports…
  • 11. Record location url config And let’s test we everything works as expected… urlpatterns = [ path('admin/', admin.site.urls), path('tracking-point/', views.TrackingPointAPIView.as_view(), name='tracking-point-api’), path('', views.IndexView.as_view(), name='tracking-index'), ]
  • 12. Testing and verifying • Run devserver • docker-compose up • Route https to local machine (serveo or ngrok) • Note with serveo – it doesn’t redirect http to https. • Open index page and store point(s) to database.
  • 13. Make tracking to stop • Add stop-tracking button <button type="button" class="btn btn-primary" onclick="stop_tracking()">Stop tracking</button> function stop_tracking() { if (!watchId) { alert("You're not tracking yet. Start tracking first."); } else { navigator.geolocation.clearWatch(watchId); watchId = null; let tracking_name = document.getElementsByName('name')[0]; tracking_name.disabled = false; } }
  • 14. List tracked points • Simple list with name of tracking and number of points in it. • A button to convert points to a linestring. {% extends "geotracker/base.html" %} {% block contents %} <form action="{% url "route-create" %}" method="post"> {% csrf_token %} <table class="table"> <thead> <tr> <th scope="col">Tracking name</th> <th scope="col">Number points</th> <th scope="col"></th> </tr> </thead> <tbody> {% for r in track_names %} <tr> <td>{{ r.name }}</td> <td>{{ r.num_points }}</td> <td><button class="btn btn-primary btn-sm" name="name" value="{{ r.name }}">Create Track</button></td> </tr> {% endfor %} </tbody> </table> </form> {% endblock %}
  • 15. List tracked points, the view class TrackingPointsListView(View): def get(self, request): track_names = ( TrackedPoint.objects.values("name") .distinct() .annotate(num_points=Count("name")) .values("name", "num_points") ) return render( request, "geotracker/tracked_points_list.html", {"track_names": track_names, "tracked_page": " active"}, ) path('tracking-points-list/', views.TrackingPointsListView.as_view(), name='tracking-points-list'),
  • 16. A view to create a linestring class RouteCreateView(View): def post(self, request): name = request.POST["name"] qs = TrackedPoint.objects.filter(name=name) # Create line points = [tp.location for tp in qs] linestring = LineString(points) RouteLine.objects.create(name=name, location=linestring) return redirect(reverse("routes-list")) path('route-create/', views.RouteCreateView.as_view(), name='route-create'),
  • 17. Add page to list lines • Add a list and show on map button {% extends "geotracker/base.html" %} {% load staticfiles %} {% block extra_head %} <script src="{% static "js/geotracker.js" %}"></script> <style> #map { height: 350px; margin-top: 16px; margin-bottom: 16px; } </style> {% endblock %}
  • 18. {% block onloadcall %}line_startup();{% endblock %} {% block contents %} <div id="map"></div> <table class="table"> <thead> <tr> <th scope="col">Tracking name</th> <th scope="col">Length (m)</th> <th scope="col"></th> </tr> </thead> <tbody> {% for r in lines %} <tr> <td>{{ r.name }}</td> <td>{{ r.route_length|floatformat:2 }}</td> <td><button class="btn btn-primary btn-sm" onclick="show_line_on_map({{ r.get_geojson_feature }})">Show on map</button></td> </tr> {% endfor %} </tbody> </table> {% endblock %} …template continued…
  • 19. Add a view to provide list of linestrings class RoutesListView(View): def get(self, request): lines = RouteLine.objects.all() return render( request, "geotracker/tracked_routes.html", {"lines": lines, "tracked_lines_page": " active"}, ) path('routes/', views.RoutesListView.as_view(), name="routes-list"),
  • 20. Aaaand… Does it work? • If it does, good. • If not, let’s try to figure out what’s wrong.
  • 21. Working with geometries • Distance calculations • Length calculations • Proximity queries
  • 22. Smoothing GPS data • We’re not going to do that…  • GPS data jumps around due inaccuracies. • Smoothing algorithms do exist. • Based on predictions of next point and statistics of previous points. • Quite common solution is to use is Kalman filter (or some derivation from that).