Files

141 lines
5.3 KiB
C++

#include "MapViewModel.h"
#include <cmath>
namespace Pyxis {
namespace MapView {
namespace {
const double EARTH_CIRCUMFERENCE_METERS = 40075016.68557849;
const double DEGREES_TO_RADIANS = 0.017453292519943295;
bool validLocation(const Telemetry::LocationTelemetry& location) {
return location.latitude_e6 >= -90000000 &&
location.latitude_e6 <= 90000000 &&
location.longitude_e6 >= -180000000 &&
location.longitude_e6 <= 180000000;
}
bool validRequest(const Request& request) {
if (!(std::isfinite(request.center.latitude) &&
std::isfinite(request.center.longitude) &&
request.center.latitude >= -90.0 &&
request.center.latitude <= 90.0 &&
request.width != 0 && request.height != 0 &&
MapProjection::isValidZoom(request.zoom) &&
request.peer_count <= MAX_MAP_PEERS &&
(request.peer_count == 0 || request.peers != nullptr))) {
return false;
}
if (request.has_local_location &&
!validLocation(request.local_location)) return false;
for (std::size_t index = 0; index < request.peer_count; ++index) {
if (!validLocation(request.peers[index].location)) return false;
}
return true;
}
MapProjection::GeoPoint pointFromLocation(
const Telemetry::LocationTelemetry& location) {
MapProjection::GeoPoint point{};
point.latitude = static_cast<double>(location.latitude_e6) / 1000000.0;
point.longitude = static_cast<double>(location.longitude_e6) / 1000000.0;
return point;
}
void appendMarker(const Telemetry::LocationTelemetry& location,
MarkerKind kind,
const Telemetry::PeerId& peer,
bool has_approx_radius,
std::uint32_t approx_radius_meters,
const MapProjection::Viewport& viewport,
std::uint32_t zoom,
Frame& output) {
MapProjection::MarkerProjection projected{};
const MapProjection::GeoPoint point = pointFromLocation(location);
if (MapProjection::projectMarker(
point, viewport, zoom, projected) !=
MapProjection::Status::OK ||
!projected.visible) {
return;
}
Marker& marker = output.markers[output.marker_count++];
marker.kind = kind;
marker.peer = peer;
marker.screen_x = projected.screen_x;
marker.screen_y = projected.screen_y;
marker.has_approx_radius = has_approx_radius;
marker.approx_radius_meters = approx_radius_meters;
marker.approx_radius_pixels = 0.0;
if (has_approx_radius && approx_radius_meters != 0U) {
const double world_pixels = static_cast<double>(MapProjection::TILE_SIZE) *
static_cast<double>(MapProjection::tileCount(zoom));
const double meters_per_pixel =
std::cos(MapProjection::clampLatitude(point.latitude) *
DEGREES_TO_RADIANS) *
EARTH_CIRCUMFERENCE_METERS / world_pixels;
if (meters_per_pixel > 0.0 && std::isfinite(meters_per_pixel)) {
marker.approx_radius_pixels =
static_cast<double>(approx_radius_meters) / meters_per_pixel;
}
}
}
} // namespace
Result buildFrame(const Request& request, Frame& output) {
if (!validRequest(request)) {
return request.peer_count > MAX_MAP_PEERS
? Result::CAPACITY_EXCEEDED
: Result::INVALID_ARGUMENT;
}
MapProjection::GlobalPixel center_pixel{};
if (MapProjection::latLonToGlobalPixel(
request.center, request.zoom, center_pixel) !=
MapProjection::Status::OK) {
return Result::INVALID_ARGUMENT;
}
MapProjection::Viewport viewport{};
viewport.left = center_pixel.x - static_cast<double>(request.width) / 2.0;
viewport.top = center_pixel.y - static_cast<double>(request.height) / 2.0;
viewport.width = request.width;
viewport.height = request.height;
std::size_t tile_count = 0;
const MapProjection::Status tile_status = MapProjection::viewportTiles(
viewport, request.zoom, request.include_tile_border,
output.tiles, MapProjection::MAX_VIEWPORT_TILES, tile_count);
if (tile_status != MapProjection::Status::OK) {
return tile_status == MapProjection::Status::VIEWPORT_TOO_LARGE ||
tile_status == MapProjection::Status::CAPACITY_EXCEEDED
? Result::VIEWPORT_TOO_LARGE
: Result::INVALID_ARGUMENT;
}
output.viewport = viewport;
output.tile_count = tile_count;
output.marker_count = 0;
const Telemetry::PeerId empty_peer{};
if (request.has_local_location) {
appendMarker(request.local_location, MarkerKind::LOCAL, empty_peer,
false, 0, viewport, request.zoom, output);
}
for (std::size_t index = 0; index < request.peer_count; ++index) {
const Telemetry::PeerLocationRecord& record = request.peers[index];
if (record.has_expiry &&
request.wall_now_millis >= record.expires_at_millis) {
continue;
}
appendMarker(record.location, MarkerKind::PEER, record.peer,
record.has_approx_radius,
record.approx_radius_meters,
viewport, request.zoom, output);
}
return Result::OK;
}
} // namespace MapView
} // namespace Pyxis