The GeoServer

The GeoServer provides support for client applications seeking to track the geographic location of large numbers of users (don’t worry, it’s not for sale to your government, and I’ll divulge your location only with your permission). Clients will typically be map-centric phone applications. We’ll concentrate only on building parts of the server, so use your imagination as to what the client might look like.

A client registers with the server to begin tracking the location of its user. The client transmits location updates to the server from time to time.

Here’s the current code for the GeoServer (including the header file for the Location class):

c9/1/GeoServerTest.cpp
 
#include "CppUTest/TestHarness.h"
 
#include "CppUTestExtensions.h"
 
#include "GeoServer.h"
 
 
using​ ​namespace​ std;
 
 
TEST_GROUP(AGeoServer) {
 
GeoServer server;
 
 
const​ ​string​ aUser{​"auser"​};
 
const​ ​double​ LocationTolerance{0.005};
 
};
 
TEST(AGeoServer, TracksAUser) {
 
server.track(aUser);
 
 
CHECK_TRUE(server.isTracking(aUser));
 
}
 
 
TEST(AGeoServer, IsNotTrackingAUserNotTracked) {
 
CHECK_FALSE(server.isTracking(aUser));
 
}
 
 
TEST(AGeoServer, TracksMultipleUsers) {
 
server.track(aUser);
 
server.track(​"anotheruser"​);
 
 
CHECK_FALSE(server.isTracking(​"thirduser"​));
 
CHECK_TRUE(server.isTracking(aUser));
 
CHECK_TRUE(server.isTracking(​"anotheruser"​));
 
}
 
 
TEST(AGeoServer, IsTrackingAnswersFalseWhenUserNoLongerTracked) {
 
server.track(aUser);
 
server.stopTracking(aUser);
 
 
CHECK_FALSE(server.isTracking(aUser));
 
}
 
 
TEST(AGeoServer, UpdatesLocationOfUser) {
 
server.track(aUser);
 
server.updateLocation(aUser, Location{38, -104});
 
 
auto​ location = server.locationOf(aUser);
 
DOUBLES_EQUAL(38, location.latitude(), LocationTolerance);
 
DOUBLES_EQUAL(-104, location.longitude(), LocationTolerance);
 
}
 
 
TEST(AGeoServer, AnswersUnknownLocationForUserNotTracked) {
 
CHECK_TRUE(server.locationOf(​"anAbUser"​).isUnknown());
 
}
 
 
TEST(AGeoServer, AnswersUnknownLocationForTrackedUserWithNoLocationUpdate) {
 
server.track(aUser);
 
CHECK_TRUE(server.locationOf(aUser).isUnknown());
 
}
 
 
TEST(AGeoServer, AnswersUnknownLocationForUserNoLongerTracked) {
 
server.track(aUser);
 
server.updateLocation(aUser, Location(40, 100));
 
server.stopTracking(aUser);
 
CHECK_TRUE(server.locationOf(aUser).isUnknown());
 
}
c9/1/GeoServer.h
 
#ifndef GeoServer_h
 
#define GeoServer_h
 
 
#include <string>
 
#include <unordered_map>
 
 
#include "Location.h"
 
 
class​ GeoServer {
 
public​:
 
void​ track(​const​ std::​string​& user);
 
void​ stopTracking(​const​ std::​string​& user);
 
void​ updateLocation(​const​ std::​string​& user, ​const​ Location& location);
 
 
bool​ isTracking(​const​ std::​string​& user) ​const​;
 
Location locationOf(​const​ std::​string​& user) ​const​;
 
 
private​:
 
std::unordered_map<std::​string​, Location> locations_;
 
 
std::unordered_map<std::​string​, Location>::const_iterator
 
find(​const​ std::​string​& user) ​const​;
 
};
 
 
#endif
c9/1/GeoServer.cpp
 
#include "GeoServer.h"
 
#include "Location.h"
 
using​ ​namespace​ std;
 
void​ GeoServer::track(​const​ ​string​& user) {
 
locations_[user] = Location();
 
}
 
 
void​ GeoServer::stopTracking(​const​ ​string​& user) {
 
locations_.erase(user);
 
}
 
 
bool​ GeoServer::isTracking(​const​ ​string​& user) ​const​ {
 
return​ find(user) != locations_.end();
 
}
 
 
void​ GeoServer::updateLocation(​const​ ​string​& user, ​const​ Location& location) {
 
locations_[user] = location;
 
}
 
Location GeoServer::locationOf(​const​ ​string​& user) ​const​ {
 
if​ (!isTracking(user)) ​return​ Location{}; ​// TODO performance cost?
 
return​ find(user)->second;
 
}
 
 
std::unordered_map<std::​string​, Location>::const_iterator
 
GeoServer::find(​const​ std::​string​& user) ​const​ {
 
return​ locations_.find(user);
 
}
c9/1/Location.h
 
#ifndef Location_h
 
#define Location_h
 
 
#include <limits>
 
#include <cmath>
 
#include <ostream>
 
 
const​ ​double​ Pi{ 4.0 * atan(1.0) };
 
const​ ​double​ ToRadiansConversionFactor{ Pi / 180 };
 
const​ ​double​ RadiusOfEarthInMeters{ 6372000 };
 
const​ ​double​ MetersPerDegreeAtEquator{ 111111 };
 
 
const​ ​double​ North{ 0 };
 
const​ ​double​ West{ 90 };
 
const​ ​double​ South{ 180 };
 
const​ ​double​ East{ 270 };
 
const​ ​double​ CloseMeters{ 3 };
 
 
class​ Location {
 
public​:
 
Location();
 
Location(​double​ latitude, ​double​ longitude);
 
 
inline​ ​double​ toRadians(​double​ degrees) ​const​ {
 
return​ degrees * ToRadiansConversionFactor;
 
}
 
 
inline​ ​double​ toCoordinate(​double​ radians) ​const​ {
 
return​ radians * (180 / Pi);
 
}
 
 
inline​ ​double​ latitudeAsRadians() ​const​ {
 
return​ toRadians(latitude_);
 
}
 
 
inline​ ​double​ longitudeAsRadians() ​const​ {
 
return​ toRadians(longitude_);
 
}
 
 
double​ latitude() ​const​;
 
double​ longitude() ​const​;
 
 
bool​ ​operator​==(​const​ Location& that);
 
bool​ ​operator​!=(​const​ Location& that);
 
 
Location go(​double​ meters, ​double​ bearing) ​const​;
 
double​ distanceInMeters(​const​ Location& there) ​const​;
 
bool​ isUnknown() ​const​;
 
bool​ isVeryCloseTo(​const​ Location& there) ​const​;
 
 
private​:
 
double​ latitude_;
 
double​ longitude_;
 
 
double​ haversineDistance(Location there) ​const​;
 
};
 
 
std::ostream& ​operator​<<(std::ostream& output, ​const​ Location& location);
 
 
#endif

You can refer to the source distribution for other source files not listed here. Hmmm...a comment in GeoServer.cpp! Someone is worried about the potential performance cost of looking up into the locations_ map twice. We’ll worry about that later (see TDD and Performance).

With the simple stuff out of the way, let’s build something meaty into the GeoServer.

Story: Retrieve Nearby Users

As a client user, I want to frequently request a list of all other users (along with their geographic coordinates) whose current position lies within a rectangular map area so that I can represent their positions on a map.

We build an implementation into GeoServer.

c9/2/GeoServerTest.cpp
 
TEST_GROUP(AGeoServer_UsersInBox) {
 
GeoServer server;
 
 
const​ ​double​ TenMeters { 10 };
 
const​ ​double​ Width { 2000 + TenMeters };
 
const​ ​double​ Height { 4000 + TenMeters};
 
const​ ​string​ aUser { ​"auser"​ };
 
const​ ​string​ bUser { ​"buser"​ };
 
const​ ​string​ cUser { ​"cuser"​ };
 
 
Location aUserLocation { 38, -103 };
 
 
void​ setup() override {
 
server.track(aUser);
 
server.track(bUser);
 
server.track(cUser);
 
server.updateLocation(aUser, aUserLocation);
 
}
 
vector<​string​> UserNames(​const​ vector<User>& users) {
 
return​ Collect<User,​string​>(users, [](User each) { ​return​ each.name(); });
 
}
 
};
 
 
TEST(AGeoServer_UsersInBox, AnswersUsersInSpecifiedRange) {
 
server.updateLocation(
 
bUser, Location{aUserLocation.go(Width / 2 - TenMeters, East)});
 
 
auto​ users = server.usersInBox(aUser, Width, Height);
 
 
CHECK_EQUAL(vector<​string​> { bUser }, UserNames(users));
 
}
 
 
TEST(AGeoServer_UsersInBox, AnswersOnlyUsersWithinSpecifiedRange) {
 
server.updateLocation(
 
bUser, Location{aUserLocation.go(Width / 2 + TenMeters, East)});
 
 
server.updateLocation(
 
cUser, Location{aUserLocation.go(Width / 2 - TenMeters, East)});
 
 
auto​ users = server.usersInBox(aUser, Width, Height);
 
 
CHECK_EQUAL(vector<​string​> { cUser }, UserNames(users));
 
}
c9/2/GeoServer.cpp
 
bool​ GeoServer::isDifferentUserInBounds(
 
const​ pair<​string​, Location>& each,
 
const​ ​string​& user,
 
const​ Area& box) ​const​ {
 
if​ (each.first == user) ​return​ false;
 
return​ box.inBounds(each.second);
 
}
 
 
vector<User> GeoServer::usersInBox(
 
const​ ​string​& user, ​double​ widthInMeters, ​double​ heightInMeters) ​const​ {
 
auto​ location = locations_.find(user)->second;
 
Area box { location, widthInMeters, heightInMeters };
 
 
vector<User> users;
 
for​ (​auto​& each: locations_)
 
if​ (isDifferentUserInBounds(each, user, box))
 
users.push_back(User{each.first, each.second});
 
return​ users;
 
}

An Area is a rectangle centered around a Location. It can answer whether it contains a(nother) location. Here’s the header file for Area:

c9/2/Area.h
 
#ifndef Area_h
 
#define Area_h
 
 
#include "Location.h"
 
 
class​ Area {
 
public​:
 
Area(​const​ Location& location, ​double​ width, ​double​ height);
 
Location upperLeft() ​const​;
 
Location upperRight() ​const​;
 
Location lowerRight() ​const​;
 
Location lowerLeft() ​const​;
 
bool​ inBounds(​const​ Location&) ​const​;
 
 
private​:
 
double​ left_;
 
double​ right_;
 
double​ top_;
 
double​ bottom_;
 
};
 
 
#endif

A User is a container holding the user’s name and a location object.

c9/2/User.h
 
#ifndef User_h
 
#define User_h
 
#include "Location.h"
 
 
class​ User {
 
public​:
 
User(​const​ std::​string​& name, Location location)
 
: name_(name), location_(location) {}
 
std::​string​ name() { ​return​ name_; }
 
Location location() { ​return​ location_; }
 
 
private​:
 
std::​string​ name_;
 
Location location_;
 
};
 
#endif
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.136.17.12