44




Peter Friese / Stefan Reichert, Zühlke Engineering


CouchDB mobile
From Couch To 5k in 1 Hour
Peter Friese




  @peterfriese
  peter.friese@zuehlke.com
  xing.to/peter
  http://peterfriese.de
Stefan Reichert




             @stefanreichert

stefan.reichert@zuehlke.com

       xing.to/stefanreichert

http://blog.wickedshell.net/
What we will cover today


 1   What is CouchDB?


 2   Serious amount of demos!


 3   Couch25K - A Runner’s App
What is CouchDB?

                   1
CouchDB is...
CouchDB is...
          A NoSQL Database.
CouchDB is...
            Document-Oriented.
   {
   !   "name": "Peter",
   !   "company": "Zühlke Engineering",
   !   "email": "peter.friese@zuehlke.com",
   !   "languages": [
   !   ! "Objective-C",
   !   ! "Java",
   !   ! "C#",
   !   ! "JavaScript"
   !   ]
   }
CouchDB is...
            Schema-Free.

                Put anything
                you like into
                your CouchDB.
                Really.
CouchDB is...
          Erlang-Powered.
CouchDB is...
                 RESTful.

 GET
 PUT            Fully embraces
 POST           HTTP verbs
 DELETE
CouchDB uses...




http://research.google.com/archive/mapreduce.html
                                                    Map/Reduce (JavaScript).
CouchDB uses...

   Map/Reduce (JavaScript).

Input
{"ip":   "212.23.45.12","traffic": “18278", date: “2012-03-11”},
{"ip":   "74.12.345.1","traffic": “345", date: “2012-03-11”},
{"ip":   "212.23.45.12","traffic": “112244", date: “2012-03-12”},
{"ip":   "212.23.45.12","traffic": “8657", date: “2012-03-13”},
{"ip":   "74.12.345.12","traffic": “12", date: “2012-03-12”},
{"ip":   "10.122.111.22","traffic": “122222", date: “2012-03-11”}
CouchDB uses...

   Map/Reduce (JavaScript).

Mapper                   Input for Reducer
212.23.45.12    18278    212.23.45.12    18278
74.12.345.1     345      212.23.45.12    112244
212.23.45.12    112244   212.23.45.12    8657
212.23.45.12    8657
74.12.345.1     12       74.12.345.1     12
10.122.111.22   122222   74.12.345.1     345

                         10.122.111.22   122222
CouchDB uses...

   Map/Reduce (JavaScript).
Input for Reducer After Reduce
212.23.45.12    18278    212.23.45.12    139179
212.23.45.12    112244
212.23.45.12    8657

74.12.345.1     12       74.12.345.1     357
74.12.345.1     345

10.122.111.22   122222   10.122.111.22   122222
CouchDB features...
       Robust Replication.
CouchDB features...
          Robust Replication.
                     Laptop


  Phone


                     Server
Demos

        2
Calling Home

  curl localhost:5984


{"couchdb":"Welcome","version":"1.1.0"}
Creating a New DB

 curl -X PUT localhost:5984/helloworld



                         {"ok":true}
Deleting a Database
 curl -X DELETE http://
 localhost:5984/helloworld


                             {"ok":true}
Creating a New Document
 curl
   -X POST
   -H "Content-Type: application/json"
   localhost:5984/helloworld
   -d @peter.json


 {"ok":true,"id":"f8e42aaa4bc77124c28
 6be13f000054e","rev":"1-4dc37117e0da
 26d9c50dc92d4cbb04cc"}
Curious? GET Your Document!
 curl http://localhost:5984/helloworld/
 f8e42aaa4bc77124c286be13f000054e



 {
     "_id": "f8e42aaa4bc77124c286be13f000054e",
     "_rev": "1-4dc37117e0da26d9c50dc92d4cbb04cc",
     "name": "Peter",
     "company": "Zühlke Engineering",
     "email": "peter.friese@zuehlke.com"
 }
Anatomy of a Document
{
    "_id": "f8e42aaa4bc77124c286be13f000054e",
    "_rev": "1-4dc37117e0da26d9c50dc92d4cbb04cc",
    "name": "Peter",
    "company": "Zühlke Engineering",
    "email": "peter.friese@zuehlke.com"
}

           UUID - very
          unique identifier
Anatomy of a Document
 {
     "_id": "f8e42aaa4bc77124c286be13f000054e",
     "_rev": "1-4dc37117e0da26d9c50dc92d4cbb04cc",
     "name": "Peter",
     "company": "Zühlke Engineering",
     "email": "peter.friese@zuehlke.com"
 }


revision revision              hash (body,
number counter                 attachment,
                              _deleted flag)
Welcome to Futon!


         Admin interface for CouchDB
         CRUD for Documents
         Manage Views
         Manage Replication
Couch25K - A Runner’s App

                     3
Basic Idea




         2-way       2-way

 Phone       IrisCouch       Phone
CouchDB Mobile




                     n e ?
                P h o
        o u r
     n y
   o
CouchDB Mobile

Your app   CouchCocoa lib     Ektorp lib   Your app




           CouchDB          CouchDB
CouchDB Mobile
 Your app   CouchCocoa lib    Ektorp lib   Your app




             TouchDB lib     TouchDB lib




             SQlite          SQlite
CouchDB Mobile


   “TouchDB is a lightweight CouchDB-
   compatible database engine suitable
   for embedding into mobile or desktop
   apps. Think of it this way: If
   CouchDB is MySQL, then TouchDB
   is SQLite.”  - Jens Alfke, Couchbase
                Labs
Start a Local Couch
 CouchTouchDBServer *server =
  [CouchTouchDBServer sharedInstance];
 NSAssert(!server.error,
  @"Error initializing TouchDB server: %@",
  server.error);

 self.database = [server
  databaseNamed:@"couch25k"];

 NSError *error;
 if (! [self.database ensureCreated:&error]) {
   // raise error
   self.connected = false;
 }
Start a Local Couch
try {
  TDServer touchDBServer = new TDServer(filesDir);
!
  HttpClient httpClient = new
   TouchDBHttpClient(touchDBServer);
  CouchDbInstance couchDBInstance =
   new StdCouchDbInstance(httpClient);
!
  CouchDbConnector couchDBConnector =
   couchDBInstance.createConnector(COUCH25K_DB,true);
! ...
} catch (IOException e) {
! Log.e(TAG, "Error starting TDServer", e);
}
Trackpoints



  {
      "run": "run-peterfriese-19",
      "user": "peterfriese",
      "lon": "9.990512659959458",
      "time": "2012-03-24 07:39:27 +0000",
      "lat": "53.73176022303823"
  }
Saving a Trackpoint
NSDictionary *trackpointProperties =
 [NSDictionary dictionaryWithObjectsAndKeys: (...)

CouchDocument *trackpointDocument =
 [database untitledDocument];

RESTOperation* op = [trackpointDocument
 putProperties:trackpointProperties];

[op onCompletion: ^{
 if (op.error)
   NSLog(@"Couldn't save the new item");
 }];
[op start];
Saving a Trackpoint
Ektorp: JPA for CouchDB
  public class TrackPoint extends CouchDbDocument


Repository Support
  public class TrackPointRepository extends
  ! CouchDbRepositorySupport<TrackPoint>


... so saving a TrackPoint is pretty easy
  trackPointRepository.add(trackPoint);
Sync with the Server


NSURL *url = [NSURL
 URLWithString:
   @"http://peterfriese.iriscouch.com/couch25k"];

[self.database replicateWithURL:url exclusively: YES];
Sync with the Server
Pull from Server
 ReplicationCommand commandPull = new
  ReplicationCommand.Builder()
    .source(COUCH25K_REMOTE_DB)
    .target(COUCH25K_DB)
    .continuous(true).build();

 try {
   couchDBInstance.replicate(commandPull);
 } catch (Exception exception) {
   Log.e(TAG, exception.getMessage(), exception);
 }
Sync with the Server
Push to Server
 ReplicationCommand commandPush = new
  ReplicationCommand.Builder()
    .source(COUCH25K_DB)
    .target(COUCH25K_REMOTE_DB)
    .continuous(true).build();

 try {
   couchDBInstance.replicate(commandPush);
 } catch (Exception exception) {
   Log.e(TAG, exception.getMessage(), exception);
 }
Demo
Display List of Runs (JavaScript)

     Map                 Reduce
                       function(keys, values)
 function(doc) {
                       {
   emit(doc.run, 1);
                         return sum(values);
 }
                       }


  run-1    1
  run-1    1
                       run-1     3
  run-1    1
                       run-2     2
  run-2    1
  run-2    1
Display List of Runs (Obj-C)

Map
[design defineViewNamed: @"runs" mapBlock: MAPBLOCK({
 id run = [doc objectForKey:@"run"];
 if (run) emit(run, nil);
})


Reduce
reduceBlock:REDUCEBLOCK({
 return [NSNumber numberWithInt:values.count];
})
Display List of Runs (Java)

Map
new TDViewMapBlock() {
  public void map(Map<String, Object> doc,
    TDViewMapEmitBlock emitter) {
    if (doc.containsKey("run")) {
      emitter.emit(doc.get("run"), doc.get("_id"));
    }
  }
}
Display List of Runs (Java)
(Re-) reduce
new TDViewReduceBlock() {
 public Object reduce(List<Object> keys, List<Object>
   values, boolean rereduce) {
   if (rereduce) {
     int sum = 0;
     for (Object object : values) {
       sum += (Integer) object;
     }
     return sum;
   }
   return values.size();
 }
};
Filtering
        Peter           Stefan

            Filter by name




    sync 2-way        sync 2-way

Phone           IrisCouch    Phone 2
Filtering
CouchDB
by_user:

 function(doc, rq) {
   if(doc.user == rq.query.username) {
     return true;
   }
   return false;
 }
Filtering
NSArray *replications =
 [self.database replicateWithURL:url exclusively: YES];

CouchPersistentReplication *from =
 [replications objectAtIndex:0];
from.continuous = YES;

from.filter = @"couch25k/by_user";
NSDictionary *filterParams = [NSDictionary
 dictionaryWithObjectsAndKeys:
   @"peterfriese", @"username", nil];
from.query_params = filterParams;
Filtering
Map<String, Object> queryParams =
 new HashMap<String, Object>();
queryParams.put("username", "stefanreichert");

ReplicationCommand commandPull = new
  ReplicationCommand.Builder()
   .source(COUCH25K_REMOTE_DB)
   .target(COUCH25K_DB).continuous(true)
   .filter("by_user")
   .queryParams(queryParams).build();
try {
! couchDBInstance.replicate(commandPull);
} catch (Exception e) {
! Log.e(TAG, exception.getMessage(), e);
}
Maps, please!
Query Trackpoints by Run
Map
mapBlock: MAPBLOCK({
 NSString *run = (NSString *)[doc objectForKey:@"run"];
 id time = [doc objectForKey:@"time"];
 NSMutableArray *key = [[NSMutableArray alloc] init];
 [key addObject:run];
 [key addObject:time];
 emit(key, doc);})

[run-peter-1,   2012-03-23   10:10]   {lat:...,   lon:...}
[run-peter-1,   2012-03-23   10:11]   {lat:...,   lon:...}
[run-peter-2,   2012-03-26   20:05]   {lat:...,   lon:...}
[run-peter-2,   2012-03-26   20:06]   {lat:...,   lon:...}
[run-peter-2,   2012-03-26   10:07]   {lat:...,   lon:...}
Query Trackpoints by Run
Query
CouchQuery *query = [[self.database
 designDocumentWithName: @"couch25k"]
 queryViewNamed: @"waypoints_by_run"];

CouchLiveQuery *livequery = [query asLiveQuery];

[livequery setStartKey:
 [NSArray arrayWithObjects:self.runKey, nil]];

[query setEndKey:
 [NSArray arrayWithObjects:self.runKey, @"ZZZ", nil]];
Query Trackpoints by Run
Map
new TDViewMapBlock() {
  public void map(Map<String, Object> document,
   TDViewMapEmitBlock emitter) {
   if (document.containsKey("run")) {
     document.get("run"), document.get("_id"));
   }
}
Query Trackpoints by Run
Query
ViewQuery viewQuery = new
 ViewQuery().designDocId("_design/TrackPoint")
   .viewName("trackpoint_by_run")
   .key(runId);
ViewResult result = db.queryView(viewQuery);
List<TrackPoint> trackPoints = new
 ArrayList<TrackPoint>();

for (Row row : result.getRows()) {
  TrackPoint trackPoint = get(row.getValue());
  trackPoints.add(trackPoint);
}
return trackPoints;
And finally...




        http://josephta.me/about-joseph-tame/
... a Tribute to Steve Jobs
                                  GPX
                                of J   cou
                                            rtes
                                     ose         y
                                         ph
                                  - T       Tam
                                      han        e
                                          ks!




         http://bit.ly/HbDRod
Relax!




 44
Thanks!
Peter Friese
Principal Consultant
                                                      rt   @peterfr
Zühlke Engineering GmbH                 @stef anreiche             iese
Am Sandtorkai 66
20457 Hamburg                        Availa
                                            ble fo
+49 151 108 604 72                   discu         r con
                                           ssing          sultin
                                                  all th         g,
                   rt
           Reiche Engineer          mobil                ings
    Stefan
            Software
                                          e and
    Senior                      H   frost
               nginee
                      rin g Gmb           y beve
             E
     Zühlke torkai 66                            rages
             nd
      Am Sa           rg
              Hambu
      20457
                           6
                 961 43 3
       + 49 173


                                                           PS: we’re hiring...

CouchDB Mobile - From Couch to 5K in 1 Hour

  • 1.
    44 Peter Friese /Stefan Reichert, Zühlke Engineering CouchDB mobile From Couch To 5k in 1 Hour
  • 2.
    Peter Friese @peterfriese peter.friese@zuehlke.com xing.to/peter http://peterfriese.de
  • 3.
    Stefan Reichert @stefanreichert stefan.reichert@zuehlke.com xing.to/stefanreichert http://blog.wickedshell.net/
  • 4.
    What we willcover today 1 What is CouchDB? 2 Serious amount of demos! 3 Couch25K - A Runner’s App
  • 5.
  • 6.
  • 7.
    CouchDB is... Document-Oriented. { ! "name": "Peter", ! "company": "Zühlke Engineering", ! "email": "peter.friese@zuehlke.com", ! "languages": [ ! ! "Objective-C", ! ! "Java", ! ! "C#", ! ! "JavaScript" ! ] }
  • 8.
    CouchDB is... Schema-Free. Put anything you like into your CouchDB. Really.
  • 9.
    CouchDB is... Erlang-Powered.
  • 10.
    CouchDB is... RESTful. GET PUT Fully embraces POST HTTP verbs DELETE
  • 11.
  • 12.
    CouchDB uses... Map/Reduce (JavaScript). Input {"ip": "212.23.45.12","traffic": “18278", date: “2012-03-11”}, {"ip": "74.12.345.1","traffic": “345", date: “2012-03-11”}, {"ip": "212.23.45.12","traffic": “112244", date: “2012-03-12”}, {"ip": "212.23.45.12","traffic": “8657", date: “2012-03-13”}, {"ip": "74.12.345.12","traffic": “12", date: “2012-03-12”}, {"ip": "10.122.111.22","traffic": “122222", date: “2012-03-11”}
  • 13.
    CouchDB uses... Map/Reduce (JavaScript). Mapper Input for Reducer 212.23.45.12 18278 212.23.45.12 18278 74.12.345.1 345 212.23.45.12 112244 212.23.45.12 112244 212.23.45.12 8657 212.23.45.12 8657 74.12.345.1 12 74.12.345.1 12 10.122.111.22 122222 74.12.345.1 345 10.122.111.22 122222
  • 14.
    CouchDB uses... Map/Reduce (JavaScript). Input for Reducer After Reduce 212.23.45.12 18278 212.23.45.12 139179 212.23.45.12 112244 212.23.45.12 8657 74.12.345.1 12 74.12.345.1 357 74.12.345.1 345 10.122.111.22 122222 10.122.111.22 122222
  • 15.
    CouchDB features... Robust Replication.
  • 16.
    CouchDB features... Robust Replication. Laptop Phone Server
  • 17.
  • 18.
    Calling Home curl localhost:5984 {"couchdb":"Welcome","version":"1.1.0"}
  • 19.
    Creating a NewDB curl -X PUT localhost:5984/helloworld {"ok":true}
  • 20.
    Deleting a Database curl -X DELETE http:// localhost:5984/helloworld {"ok":true}
  • 21.
    Creating a NewDocument curl -X POST -H "Content-Type: application/json" localhost:5984/helloworld -d @peter.json {"ok":true,"id":"f8e42aaa4bc77124c28 6be13f000054e","rev":"1-4dc37117e0da 26d9c50dc92d4cbb04cc"}
  • 22.
    Curious? GET YourDocument! curl http://localhost:5984/helloworld/ f8e42aaa4bc77124c286be13f000054e { "_id": "f8e42aaa4bc77124c286be13f000054e", "_rev": "1-4dc37117e0da26d9c50dc92d4cbb04cc", "name": "Peter", "company": "Zühlke Engineering", "email": "peter.friese@zuehlke.com" }
  • 23.
    Anatomy of aDocument { "_id": "f8e42aaa4bc77124c286be13f000054e", "_rev": "1-4dc37117e0da26d9c50dc92d4cbb04cc", "name": "Peter", "company": "Zühlke Engineering", "email": "peter.friese@zuehlke.com" } UUID - very unique identifier
  • 24.
    Anatomy of aDocument { "_id": "f8e42aaa4bc77124c286be13f000054e", "_rev": "1-4dc37117e0da26d9c50dc92d4cbb04cc", "name": "Peter", "company": "Zühlke Engineering", "email": "peter.friese@zuehlke.com" } revision revision hash (body, number counter attachment, _deleted flag)
  • 25.
    Welcome to Futon! Admin interface for CouchDB CRUD for Documents Manage Views Manage Replication
  • 26.
    Couch25K - ARunner’s App 3
  • 27.
    Basic Idea 2-way 2-way Phone IrisCouch Phone
  • 28.
    CouchDB Mobile n e ? P h o o u r n y o
  • 29.
    CouchDB Mobile Your app CouchCocoa lib Ektorp lib Your app CouchDB CouchDB
  • 30.
    CouchDB Mobile Yourapp CouchCocoa lib Ektorp lib Your app TouchDB lib TouchDB lib SQlite SQlite
  • 31.
    CouchDB Mobile “TouchDB is a lightweight CouchDB- compatible database engine suitable for embedding into mobile or desktop apps. Think of it this way: If CouchDB is MySQL, then TouchDB is SQLite.” - Jens Alfke, Couchbase Labs
  • 32.
    Start a LocalCouch CouchTouchDBServer *server = [CouchTouchDBServer sharedInstance]; NSAssert(!server.error, @"Error initializing TouchDB server: %@", server.error); self.database = [server databaseNamed:@"couch25k"]; NSError *error; if (! [self.database ensureCreated:&error]) { // raise error self.connected = false; }
  • 33.
    Start a LocalCouch try { TDServer touchDBServer = new TDServer(filesDir); ! HttpClient httpClient = new TouchDBHttpClient(touchDBServer); CouchDbInstance couchDBInstance = new StdCouchDbInstance(httpClient); ! CouchDbConnector couchDBConnector = couchDBInstance.createConnector(COUCH25K_DB,true); ! ... } catch (IOException e) { ! Log.e(TAG, "Error starting TDServer", e); }
  • 34.
    Trackpoints { "run": "run-peterfriese-19", "user": "peterfriese", "lon": "9.990512659959458", "time": "2012-03-24 07:39:27 +0000", "lat": "53.73176022303823" }
  • 35.
    Saving a Trackpoint NSDictionary*trackpointProperties = [NSDictionary dictionaryWithObjectsAndKeys: (...) CouchDocument *trackpointDocument = [database untitledDocument]; RESTOperation* op = [trackpointDocument putProperties:trackpointProperties]; [op onCompletion: ^{ if (op.error) NSLog(@"Couldn't save the new item"); }]; [op start];
  • 36.
    Saving a Trackpoint Ektorp:JPA for CouchDB public class TrackPoint extends CouchDbDocument Repository Support public class TrackPointRepository extends ! CouchDbRepositorySupport<TrackPoint> ... so saving a TrackPoint is pretty easy trackPointRepository.add(trackPoint);
  • 37.
    Sync with theServer NSURL *url = [NSURL URLWithString: @"http://peterfriese.iriscouch.com/couch25k"]; [self.database replicateWithURL:url exclusively: YES];
  • 38.
    Sync with theServer Pull from Server ReplicationCommand commandPull = new ReplicationCommand.Builder() .source(COUCH25K_REMOTE_DB) .target(COUCH25K_DB) .continuous(true).build(); try { couchDBInstance.replicate(commandPull); } catch (Exception exception) { Log.e(TAG, exception.getMessage(), exception); }
  • 39.
    Sync with theServer Push to Server ReplicationCommand commandPush = new ReplicationCommand.Builder() .source(COUCH25K_DB) .target(COUCH25K_REMOTE_DB) .continuous(true).build(); try { couchDBInstance.replicate(commandPush); } catch (Exception exception) { Log.e(TAG, exception.getMessage(), exception); }
  • 40.
  • 41.
    Display List ofRuns (JavaScript) Map Reduce function(keys, values) function(doc) { { emit(doc.run, 1); return sum(values); } } run-1 1 run-1 1 run-1 3 run-1 1 run-2 2 run-2 1 run-2 1
  • 42.
    Display List ofRuns (Obj-C) Map [design defineViewNamed: @"runs" mapBlock: MAPBLOCK({ id run = [doc objectForKey:@"run"]; if (run) emit(run, nil); }) Reduce reduceBlock:REDUCEBLOCK({ return [NSNumber numberWithInt:values.count]; })
  • 43.
    Display List ofRuns (Java) Map new TDViewMapBlock() { public void map(Map<String, Object> doc, TDViewMapEmitBlock emitter) { if (doc.containsKey("run")) { emitter.emit(doc.get("run"), doc.get("_id")); } } }
  • 44.
    Display List ofRuns (Java) (Re-) reduce new TDViewReduceBlock() { public Object reduce(List<Object> keys, List<Object> values, boolean rereduce) { if (rereduce) { int sum = 0; for (Object object : values) { sum += (Integer) object; } return sum; } return values.size(); } };
  • 45.
    Filtering Peter Stefan Filter by name sync 2-way sync 2-way Phone IrisCouch Phone 2
  • 46.
    Filtering CouchDB by_user: function(doc, rq){ if(doc.user == rq.query.username) { return true; } return false; }
  • 47.
    Filtering NSArray *replications = [self.database replicateWithURL:url exclusively: YES]; CouchPersistentReplication *from = [replications objectAtIndex:0]; from.continuous = YES; from.filter = @"couch25k/by_user"; NSDictionary *filterParams = [NSDictionary dictionaryWithObjectsAndKeys: @"peterfriese", @"username", nil]; from.query_params = filterParams;
  • 48.
    Filtering Map<String, Object> queryParams= new HashMap<String, Object>(); queryParams.put("username", "stefanreichert"); ReplicationCommand commandPull = new ReplicationCommand.Builder() .source(COUCH25K_REMOTE_DB) .target(COUCH25K_DB).continuous(true) .filter("by_user") .queryParams(queryParams).build(); try { ! couchDBInstance.replicate(commandPull); } catch (Exception e) { ! Log.e(TAG, exception.getMessage(), e); }
  • 49.
  • 50.
    Query Trackpoints byRun Map mapBlock: MAPBLOCK({ NSString *run = (NSString *)[doc objectForKey:@"run"]; id time = [doc objectForKey:@"time"]; NSMutableArray *key = [[NSMutableArray alloc] init]; [key addObject:run]; [key addObject:time]; emit(key, doc);}) [run-peter-1, 2012-03-23 10:10] {lat:..., lon:...} [run-peter-1, 2012-03-23 10:11] {lat:..., lon:...} [run-peter-2, 2012-03-26 20:05] {lat:..., lon:...} [run-peter-2, 2012-03-26 20:06] {lat:..., lon:...} [run-peter-2, 2012-03-26 10:07] {lat:..., lon:...}
  • 51.
    Query Trackpoints byRun Query CouchQuery *query = [[self.database designDocumentWithName: @"couch25k"] queryViewNamed: @"waypoints_by_run"]; CouchLiveQuery *livequery = [query asLiveQuery]; [livequery setStartKey: [NSArray arrayWithObjects:self.runKey, nil]]; [query setEndKey: [NSArray arrayWithObjects:self.runKey, @"ZZZ", nil]];
  • 52.
    Query Trackpoints byRun Map new TDViewMapBlock() { public void map(Map<String, Object> document, TDViewMapEmitBlock emitter) { if (document.containsKey("run")) { document.get("run"), document.get("_id")); } }
  • 53.
    Query Trackpoints byRun Query ViewQuery viewQuery = new ViewQuery().designDocId("_design/TrackPoint") .viewName("trackpoint_by_run") .key(runId); ViewResult result = db.queryView(viewQuery); List<TrackPoint> trackPoints = new ArrayList<TrackPoint>(); for (Row row : result.getRows()) { TrackPoint trackPoint = get(row.getValue()); trackPoints.add(trackPoint); } return trackPoints;
  • 54.
    And finally... http://josephta.me/about-joseph-tame/
  • 55.
    ... a Tributeto Steve Jobs GPX of J cou rtes ose y ph - T Tam han e ks! http://bit.ly/HbDRod
  • 56.
  • 57.
    Thanks! Peter Friese Principal Consultant rt @peterfr Zühlke Engineering GmbH @stef anreiche iese Am Sandtorkai 66 20457 Hamburg Availa ble fo +49 151 108 604 72 discu r con ssing sultin all th g, rt Reiche Engineer mobil ings Stefan Software e and Senior H frost nginee rin g Gmb y beve E Zühlke torkai 66 rages nd Am Sa rg Hambu 20457 6 961 43 3 + 49 173 PS: we’re hiring...