offline first development - glasgow php - january 2016
TRANSCRIPT
![Page 1: Offline first development - Glasgow PHP - January 2016](https://reader035.vdocuments.mx/reader035/viewer/2022062503/588aeb6f1a28abab6c8b6d35/html5/thumbnails/1.jpg)
@glynn_bird
Offline-First DevelopmentGlasgowPHP – January 2016
Glynn Bird, Developer Advocate, IBM
@glynn_bird
![Page 2: Offline first development - Glasgow PHP - January 2016](https://reader035.vdocuments.mx/reader035/viewer/2022062503/588aeb6f1a28abab6c8b6d35/html5/thumbnails/2.jpg)
@glynn_bird
NoSQL…
![Page 3: Offline first development - Glasgow PHP - January 2016](https://reader035.vdocuments.mx/reader035/viewer/2022062503/588aeb6f1a28abab6c8b6d35/html5/thumbnails/3.jpg)
@glynn_bird
Relational Databases• Defined Schema
• Tables/Columns/Rows
• Constraints/Stored Procedures/Triggers
• Indexes/Views
• Structured Query Language (SQL)
![Page 4: Offline first development - Glasgow PHP - January 2016](https://reader035.vdocuments.mx/reader035/viewer/2022062503/588aeb6f1a28abab6c8b6d35/html5/thumbnails/4.jpg)
@glynn_bird
NoSQL Databases• Any database that isn't a RDBMS!• Types include:• Document stores• Key/Value stores• Graph databases• Often schemaless• Often without SQL
![Page 5: Offline first development - Glasgow PHP - January 2016](https://reader035.vdocuments.mx/reader035/viewer/2022062503/588aeb6f1a28abab6c8b6d35/html5/thumbnails/5.jpg)
@glynn_bird
…On the Move
![Page 6: Offline first development - Glasgow PHP - January 2016](https://reader035.vdocuments.mx/reader035/viewer/2022062503/588aeb6f1a28abab6c8b6d35/html5/thumbnails/6.jpg)
@glynn_bird6
![Page 7: Offline first development - Glasgow PHP - January 2016](https://reader035.vdocuments.mx/reader035/viewer/2022062503/588aeb6f1a28abab6c8b6d35/html5/thumbnails/7.jpg)
@glynn_bird7
![Page 8: Offline first development - Glasgow PHP - January 2016](https://reader035.vdocuments.mx/reader035/viewer/2022062503/588aeb6f1a28abab6c8b6d35/html5/thumbnails/8.jpg)
@glynn_bird8
![Page 9: Offline first development - Glasgow PHP - January 2016](https://reader035.vdocuments.mx/reader035/viewer/2022062503/588aeb6f1a28abab6c8b6d35/html5/thumbnails/9.jpg)
@glynn_bird
Image Credit: Joan Touzet (@wohali), ASF Member, CouchDB PMC Member
9
![Page 10: Offline first development - Glasgow PHP - January 2016](https://reader035.vdocuments.mx/reader035/viewer/2022062503/588aeb6f1a28abab6c8b6d35/html5/thumbnails/10.jpg)
@glynn_bird
Frameworks
10
![Page 11: Offline first development - Glasgow PHP - January 2016](https://reader035.vdocuments.mx/reader035/viewer/2022062503/588aeb6f1a28abab6c8b6d35/html5/thumbnails/11.jpg)
@glynn_bird
Image Credit: Device landscape by Jeremy Keith, on Flickr
11
Not just mobile first…
![Page 12: Offline first development - Glasgow PHP - January 2016](https://reader035.vdocuments.mx/reader035/viewer/2022062503/588aeb6f1a28abab6c8b6d35/html5/thumbnails/12.jpg)
@glynn_bird
Image Credit: NASA New Horizons
12
Offline First
Offline-First
![Page 13: Offline first development - Glasgow PHP - January 2016](https://reader035.vdocuments.mx/reader035/viewer/2022062503/588aeb6f1a28abab6c8b6d35/html5/thumbnails/13.jpg)
@glynn_bird
Offline, online and somewhere in-between
13
![Page 14: Offline first development - Glasgow PHP - January 2016](https://reader035.vdocuments.mx/reader035/viewer/2022062503/588aeb6f1a28abab6c8b6d35/html5/thumbnails/14.jpg)
@glynn_bird
The Eight Fallacies of Distributed Computing1. The network is reliable
2. Latency is zero
3. Bandwidth is infinite
4. The network is secure
5. Topology doesn't change
6. There is one administrator
7. Transport cost is zero
8. The network is homogeneous
14
Text Credit: The Eight Fallacies of Distributed Computing by Peter Deutsch | Image Credit: Pneumatic Central by Sleestak, on Flickr
![Page 15: Offline first development - Glasgow PHP - January 2016](https://reader035.vdocuments.mx/reader035/viewer/2022062503/588aeb6f1a28abab6c8b6d35/html5/thumbnails/15.jpg)
@glynn_bird15
Offline-first is the only way
to achieve a true, 100% always-on user
experience.**assuming the device is reliable
![Page 16: Offline first development - Glasgow PHP - January 2016](https://reader035.vdocuments.mx/reader035/viewer/2022062503/588aeb6f1a28abab6c8b6d35/html5/thumbnails/16.jpg)
@glynn_bird
Benefits of Offline First
16
• Better, faster user experience, both offline and online• Allow your users to work offline or with limited connectivity• Potentially saves battery life and bandwidth usage
![Page 17: Offline first development - Glasgow PHP - January 2016](https://reader035.vdocuments.mx/reader035/viewer/2022062503/588aeb6f1a28abab6c8b6d35/html5/thumbnails/17.jpg)
@glynn_bird
Offline Patterns & Anti-Patterns• Don't return an error for no reason
• Do let users view cached/saved data
• Do synchronize data when connected
• Consider letting your users decide when to sync
• Think about the UX of users seeing stale data
17
![Page 18: Offline first development - Glasgow PHP - January 2016](https://reader035.vdocuments.mx/reader035/viewer/2022062503/588aeb6f1a28abab6c8b6d35/html5/thumbnails/18.jpg)
@glynn_bird
Difficulties of Offline First
18
Image credit http://www.sneakerheadvc.com/wp-content/uploads/2012/02/Apple_iSync1.png
![Page 19: Offline first development - Glasgow PHP - January 2016](https://reader035.vdocuments.mx/reader035/viewer/2022062503/588aeb6f1a28abab6c8b6d35/html5/thumbnails/19.jpg)
@glynn_bird
Introducing CouchDB & IBM Cloudant
![Page 20: Offline first development - Glasgow PHP - January 2016](https://reader035.vdocuments.mx/reader035/viewer/2022062503/588aeb6f1a28abab6c8b6d35/html5/thumbnails/20.jpg)
@glynn_bird
Apache CouchDB• JSON document store
• HTTP API
• Replication
• Free, open-source
20
![Page 21: Offline first development - Glasgow PHP - January 2016](https://reader035.vdocuments.mx/reader035/viewer/2022062503/588aeb6f1a28abab6c8b6d35/html5/thumbnails/21.jpg)
@glynn_bird
Apache CouchDB – built to replicate• MVCC for document versioning
• Replication
• One-way or Two-way
• One-shot or continuous
21
![Page 22: Offline first development - Glasgow PHP - January 2016](https://reader035.vdocuments.mx/reader035/viewer/2022062503/588aeb6f1a28abab6c8b6d35/html5/thumbnails/22.jpg)
@glynn_bird
Apache CouchDB – built to replicate
22
2.0
multi-node clustering
Cloudant Geo
Cloudant Query (Mango)
Cloudant Search (Lucene)
Dashboard
![Page 23: Offline first development - Glasgow PHP - January 2016](https://reader035.vdocuments.mx/reader035/viewer/2022062503/588aeb6f1a28abab6c8b6d35/html5/thumbnails/23.jpg)
@glynn_bird
IBM Cloudant – built for scale
23
![Page 24: Offline first development - Glasgow PHP - January 2016](https://reader035.vdocuments.mx/reader035/viewer/2022062503/588aeb6f1a28abab6c8b6d35/html5/thumbnails/24.jpg)
@glynn_bird
IBM Cloudant• Globally distributed data layer for
web and mobile applications• Run as-a-service
• MongoDB-style queries
• Advanced geospatial capabilities
• Full text search indexing
24
![Page 25: Offline first development - Glasgow PHP - January 2016](https://reader035.vdocuments.mx/reader035/viewer/2022062503/588aeb6f1a28abab6c8b6d35/html5/thumbnails/25.jpg)
@glynn_bird
Mobile Apps - PouchDB
![Page 26: Offline first development - Glasgow PHP - January 2016](https://reader035.vdocuments.mx/reader035/viewer/2022062503/588aeb6f1a28abab6c8b6d35/html5/thumbnails/26.jpg)
@glynn_bird
PouchDB• A database in your web browser
• Can synchronize with any database that implements the CouchDB Replication Protocol
• Makes create, read, update and delete operations extremely quickly
• Free, open-source
26
![Page 27: Offline first development - Glasgow PHP - January 2016](https://reader035.vdocuments.mx/reader035/viewer/2022062503/588aeb6f1a28abab6c8b6d35/html5/thumbnails/27.jpg)
@glynn_bird
Getting started with PouchDB
27
<script src="https://cdn.jsdelivr.net/pouchdb/5.1.0/pouchdb.min.js"></script>
<script type="javascript">
var db = new PouchDB("smart-meter");
</script>
![Page 28: Offline first development - Glasgow PHP - January 2016](https://reader035.vdocuments.mx/reader035/viewer/2022062503/588aeb6f1a28abab6c8b6d35/html5/thumbnails/28.jpg)
@glynn_bird
Adding documents - callbacks
28
db.post({ date: "2014-11-12T23:27:03.794Z", kilowatt_hours: 14}, function(err, data) { console.log(err,data);});
![Page 29: Offline first development - Glasgow PHP - January 2016](https://reader035.vdocuments.mx/reader035/viewer/2022062503/588aeb6f1a28abab6c8b6d35/html5/thumbnails/29.jpg)
@glynn_bird
Adding documents - bring your own id
29
var db = new PouchDB("smart-meter");var obj = { _id: "abc123", timestamp: "2014-11-12T23:27:03.794Z", kilowatt_hours: 14};db.put(obj, callback);
https://github.com/bradley-holt/offline-first/blob/master/pouchdb/04-create-document-put.js
![Page 30: Offline first development - Glasgow PHP - January 2016](https://reader035.vdocuments.mx/reader035/viewer/2022062503/588aeb6f1a28abab6c8b6d35/html5/thumbnails/30.jpg)
@glynn_bird
Getting a document
30
db.get("abc123", callback);
// calls back with // {// _id: "abc123",// _rev: "1-27695d5f483ac267d03ad0e3cb54bd2c",// timestamp: "2014-11-12T23:27:03.794Z",// kilowatt_hours: 14// }
![Page 31: Offline first development - Glasgow PHP - January 2016](https://reader035.vdocuments.mx/reader035/viewer/2022062503/588aeb6f1a28abab6c8b6d35/html5/thumbnails/31.jpg)
@glynn_bird
Getting multiple documents
31
db.allDocs({include_docs:true}, callback);
// calls back with // {// "offset": 0,// "total_rows": 24,// "rows": [{...},{...}]// }
![Page 32: Offline first development - Glasgow PHP - January 2016](https://reader035.vdocuments.mx/reader035/viewer/2022062503/588aeb6f1a28abab6c8b6d35/html5/thumbnails/32.jpg)
@glynn_bird
Querying
32
• Primary Index
• MapReduce
• PouchDB-find plugin
• PouchDB-quick search plugin
![Page 33: Offline first development - Glasgow PHP - January 2016](https://reader035.vdocuments.mx/reader035/viewer/2022062503/588aeb6f1a28abab6c8b6d35/html5/thumbnails/33.jpg)
@glynn_bird
Querying a Database with PouchDB Find• Based on Cloudant Query (Mango)
• MongoDB-style query language
33
Image Credit: Mango with section on a white background by bangdoll, on Flickr
db.find({ selector: { name: 'Mario' debut: { '$gt': 1990 } }, fields: ['_id', 'lastname'], sort: ['lastname']})...
![Page 34: Offline first development - Glasgow PHP - January 2016](https://reader035.vdocuments.mx/reader035/viewer/2022062503/588aeb6f1a28abab6c8b6d35/html5/thumbnails/34.jpg)
@glynn_bird
Replicating a PouchDB Database
34
var db = new PouchDB("smart-meter");var remoteDb = new PouchDB("https://bradley-holt.cloudant.com/smart-meter");db.replicateTo(remoteDb);
https://github.com/bradley-holt/offline-first/blob/master/pouchdb/08-replicate-database.js
![Page 35: Offline first development - Glasgow PHP - January 2016](https://reader035.vdocuments.mx/reader035/viewer/2022062503/588aeb6f1a28abab6c8b6d35/html5/thumbnails/35.jpg)
@glynn_bird
Bidirectionally Replicating a PouchDB Database
35
db.sync(remoteDb).on("change", function(info) { // Replication has written a new document console.log(info); }).on("complete", function(info) { // Replication has completed or been cancelled console.log(info); });
https://github.com/bradley-holt/offline-first/blob/master/pouchdb/09-replicate-database-bidirectional.js
![Page 36: Offline first development - Glasgow PHP - January 2016](https://reader035.vdocuments.mx/reader035/viewer/2022062503/588aeb6f1a28abab6c8b6d35/html5/thumbnails/36.jpg)
@glynn_bird
Listening for Database Changes
36
var changes = remoteDb.changes({ since: "now"}).on("change", function(change) { // A document has changed console.log(change);}).on("complete", function(info) { // changes() was canceled console.log(info);});
https://github.com/bradley-holt/offline-first/blob/master/pouchdb/11-database-changes.js
![Page 37: Offline first development - Glasgow PHP - January 2016](https://reader035.vdocuments.mx/reader035/viewer/2022062503/588aeb6f1a28abab6c8b6d35/html5/thumbnails/37.jpg)
@glynn_bird
PouchDB Framework Adapters
37
![Page 38: Offline first development - Glasgow PHP - January 2016](https://reader035.vdocuments.mx/reader035/viewer/2022062503/588aeb6f1a28abab6c8b6d35/html5/thumbnails/38.jpg)
@glynn_bird
Web Apps Going Offline
![Page 39: Offline first development - Glasgow PHP - January 2016](https://reader035.vdocuments.mx/reader035/viewer/2022062503/588aeb6f1a28abab6c8b6d35/html5/thumbnails/39.jpg)
@glynn_bird
HTML5 Offline Application Cache• Enables fully-functional offline
web apps
• Stores files and assets for offline browsing
• Makes page loads very fast, even when online
39
![Page 40: Offline first development - Glasgow PHP - January 2016](https://reader035.vdocuments.mx/reader035/viewer/2022062503/588aeb6f1a28abab6c8b6d35/html5/thumbnails/40.jpg)
@glynn_bird
Cache Manifest File
40
<html manifest="example.appcache"> …</html>
CACHE MANIFEST# v1 - 2015-01-08index.htmllogo.pngapp.cssapp.js
![Page 41: Offline first development - Glasgow PHP - January 2016](https://reader035.vdocuments.mx/reader035/viewer/2022062503/588aeb6f1a28abab6c8b6d35/html5/thumbnails/41.jpg)
@glynn_bird
Service Workers (Beta)
41
Client-side scripting framework for
•programmable cache
•sync
•push messaging
•geo-fencing
•background tasks
https://jakearchibald.github.io/isserviceworkerready/
![Page 42: Offline first development - Glasgow PHP - January 2016](https://reader035.vdocuments.mx/reader035/viewer/2022062503/588aeb6f1a28abab6c8b6d35/html5/thumbnails/42.jpg)
@glynn_bird
Hybrid or Responsive Mobile Web Apps
![Page 43: Offline first development - Glasgow PHP - January 2016](https://reader035.vdocuments.mx/reader035/viewer/2022062503/588aeb6f1a28abab6c8b6d35/html5/thumbnails/43.jpg)
@glynn_bird
Hybrid Mobile Web Apps• Native mobile web apps built with
HTML5, CSS and JavaScript
• Good for:• Fully-featured, cross-platform native apps
• High-fidelity prototypes
43
![Page 44: Offline first development - Glasgow PHP - January 2016](https://reader035.vdocuments.mx/reader035/viewer/2022062503/588aeb6f1a28abab6c8b6d35/html5/thumbnails/44.jpg)
@glynn_bird
Responsive Mobile Web Apps• HTML5, CSS and JavaScript mobile
web apps
• Responsive design
• Enhanced to enable offline usage
44
![Page 45: Offline first development - Glasgow PHP - January 2016](https://reader035.vdocuments.mx/reader035/viewer/2022062503/588aeb6f1a28abab6c8b6d35/html5/thumbnails/45.jpg)
@glynn_bird
Native iOS & Android Apps
![Page 46: Offline first development - Glasgow PHP - January 2016](https://reader035.vdocuments.mx/reader035/viewer/2022062503/588aeb6f1a28abab6c8b6d35/html5/thumbnails/46.jpg)
@glynn_bird
Cloudant Sync• Library for iOS and Android
• Provides local storage and query API
• Optional sync to Cloudant
• Open-source and free to use
![Page 47: Offline first development - Glasgow PHP - January 2016](https://reader035.vdocuments.mx/reader035/viewer/2022062503/588aeb6f1a28abab6c8b6d35/html5/thumbnails/47.jpg)
@glynn_bird
Cloudant Sync• Stores data using SQLite
• TouchDB provides MVCC
• Replication to Cloudant over HTTPS
• Cloudant Query API
![Page 48: Offline first development - Glasgow PHP - January 2016](https://reader035.vdocuments.mx/reader035/viewer/2022062503/588aeb6f1a28abab6c8b6d35/html5/thumbnails/48.jpg)
@glynn_bird
Getting started with Cloudant Sync - iOS
48
CDTDatastore *ds = [manager datastoreNamed:@"mydb" error:&error];
pod "CDTDatastore"
• Install library
• Create database
![Page 49: Offline first development - Glasgow PHP - January 2016](https://reader035.vdocuments.mx/reader035/viewer/2022062503/588aeb6f1a28abab6c8b6d35/html5/thumbnails/49.jpg)
@glynn_bird
Creating data Cloudant Sync - iOS
49
CDTDocumentRevision *rev = [CDTDocumentRevision revisionWithDocId:@"doc1"];
rev.body = [@{ @"description": @"Buy milk", @"completed": @NO, @"type": @"com.cloudant.sync.example.task"} mutableCopy];
CDTDocumentRevision *revision = [ds createDocumentFromRevision:rev error:&error];
![Page 50: Offline first development - Glasgow PHP - January 2016](https://reader035.vdocuments.mx/reader035/viewer/2022062503/588aeb6f1a28abab6c8b6d35/html5/thumbnails/50.jpg)
@glynn_bird
Fetching data - Cloudant Sync - iOS
50
CDTDocumentRevision *retrieved = [datastore getDocumentWithId:@"doc1" error:&error];
![Page 51: Offline first development - Glasgow PHP - January 2016](https://reader035.vdocuments.mx/reader035/viewer/2022062503/588aeb6f1a28abab6c8b6d35/html5/thumbnails/51.jpg)
@glynn_bird
Indexing data - Cloudant Sync - iOS
51
// create index on name, age and pet species NSString *name = [ds ensureIndexed:@[@"name", @"age", @"pet.species"] withName:@"basic"]
// create full-text index on name and comment NSString *name = [ds ensureIndexed:@[@"name", @"comment"] withName:@"basic_text_index" type:@"text"];
![Page 52: Offline first development - Glasgow PHP - January 2016](https://reader035.vdocuments.mx/reader035/viewer/2022062503/588aeb6f1a28abab6c8b6d35/html5/thumbnails/52.jpg)
@glynn_bird
Querying data - Cloudant Sync - iOS
52
NSDictionary *query1 = @{ @"pet.species": @"cat" };
NSDictionary *query2 = @{ @"$or": @[ @{ @"pet.species": @{ @"$eq": @"dog" } }, @{ @"age": @{ @"$lt": @30 } } ]};
CDTQResultSet *result = [ds find:query];
![Page 53: Offline first development - Glasgow PHP - January 2016](https://reader035.vdocuments.mx/reader035/viewer/2022062503/588aeb6f1a28abab6c8b6d35/html5/thumbnails/53.jpg)
@glynn_bird
Getting started with Cloudant Sync - Android
53
repositories { mavenLocal() maven { url "http://cloudant.github.io/cloudant-sync-eap/repository/" } mavenCentral()}
dependencies { compile group: 'com.cloudant', name: 'cloudant-sync-datastore-core', version:'0.14.0' compile group: 'com.cloudant', name: 'cloudant-sync-datastore-android', version:'0.14.0'}
• Install library
![Page 54: Offline first development - Glasgow PHP - January 2016](https://reader035.vdocuments.mx/reader035/viewer/2022062503/588aeb6f1a28abab6c8b6d35/html5/thumbnails/54.jpg)
@glynn_bird
Creating a database - Cloudant Sync - Android
54
import com.cloudant.sync.datastore.*;
// Create a DatastoreManager using application internal storage path…
Datastore ds = manager.openDatastore("mydb");
![Page 55: Offline first development - Glasgow PHP - January 2016](https://reader035.vdocuments.mx/reader035/viewer/2022062503/588aeb6f1a28abab6c8b6d35/html5/thumbnails/55.jpg)
@glynn_bird
Storing data - Cloudant Sync - Android
55
MutableDocumentRevision revision = new MutableDocumentRevision();
Map<String, Object> body = new HashMap<String, Object>();
body.put("animal", "cat");
revision.body = DocumentBodyFactory.create(body);
BasicDocumentRevision saved = ds.createDocumentFromRevision(revision);
![Page 56: Offline first development - Glasgow PHP - January 2016](https://reader035.vdocuments.mx/reader035/viewer/2022062503/588aeb6f1a28abab6c8b6d35/html5/thumbnails/56.jpg)
@glynn_bird
Pro Forma• Define fields you want to collect
• Renders form saving data to PouchDB
• Replicates data to Cloudant
• Demohttps://glynnbird.github.io/proforma/
56
![Page 57: Offline first development - Glasgow PHP - January 2016](https://reader035.vdocuments.mx/reader035/viewer/2022062503/588aeb6f1a28abab6c8b6d35/html5/thumbnails/57.jpg)
@glynn_bird
MD• Offline word processor
• Saves Markdown documents to PouchDB
• Replicates data to Cloudant
• Demohttp://mddoc.mybluemix.net/
57
![Page 58: Offline first development - Glasgow PHP - January 2016](https://reader035.vdocuments.mx/reader035/viewer/2022062503/588aeb6f1a28abab6c8b6d35/html5/thumbnails/58.jpg)
@glynn_bird
Gutenberg• Offline e-book reader
• Replicates book list from server
• Each book is a Cloudant database
• Demohttp://glynnbird.github.io/gutenberg/
58
![Page 59: Offline first development - Glasgow PHP - January 2016](https://reader035.vdocuments.mx/reader035/viewer/2022062503/588aeb6f1a28abab6c8b6d35/html5/thumbnails/59.jpg)
@glynn_bird
www.glynnbird.com• My home page
• Cloudant database of articles
• Replicated to PouchDB
• Appcache for offline first
• http://www.glynnbird.com/
59
![Page 60: Offline first development - Glasgow PHP - January 2016](https://reader035.vdocuments.mx/reader035/viewer/2022062503/588aeb6f1a28abab6c8b6d35/html5/thumbnails/60.jpg)
@glynn_bird
Volt• Password vault in a Chrome
extension
• Data stored in encrypted in PouchDB
• Optional back to CouchDB/Cloudant
• https://github.com/glynnbird/volt
60
![Page 61: Offline first development - Glasgow PHP - January 2016](https://reader035.vdocuments.mx/reader035/viewer/2022062503/588aeb6f1a28abab6c8b6d35/html5/thumbnails/61.jpg)
@glynn_bird
Location Tracker• Stores data locally in PouchDB
• Front end built with AngularJS
• Authentication logic built with Node.js
• User interface built with Leaflet
• Replicates location data to Cloudant
• More info:https://cloudant.com/location-tracker/
61
![Page 62: Offline first development - Glasgow PHP - January 2016](https://reader035.vdocuments.mx/reader035/viewer/2022062503/588aeb6f1a28abab6c8b6d35/html5/thumbnails/62.jpg)
@glynn_bird
Glynn BirdDeveloper Advocate, Cloud Data [email protected]@glynn_birdgithub.com/glynnbird
![Page 63: Offline first development - Glasgow PHP - January 2016](https://reader035.vdocuments.mx/reader035/viewer/2022062503/588aeb6f1a28abab6c8b6d35/html5/thumbnails/63.jpg)
@glynn_bird
Image Credits
63
• Joan Touzet (@wohali), ASF Member, CouchDB PMC Member<https://twitter.com/wohali/status/595689720933445632>
• Device landscape by Jeremy Keith, on Flickr<https://www.flickr.com/photos/adactio/6153481666>
• NASA New Horizons<https://www.nasa.gov/sites/default/files/thumbnails/image/nh-surface.jpg>
• Pneumatic Central by Sleestak, on Flickr<https://www.flickr.com/photos/dlanod/235990854>
• Mango with section on a white background by bangdoll, on Flickr<https://www.flickr.com/photos/bangdoll/5665235102>
• Grunge Warning Sign - Do Not Read This Sign by Nicolas Raymond, on Flickr<https://www.flickr.com/photos/80497449@N04/7417352980>