/**************************************************************************** * * Copyright (c) 2013 PX4 Development Team. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * 3. Neither the name PX4 nor the names of its contributors may be * used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * ****************************************************************************/ /** * @file geofence.cpp * Provides functions for handling the geofence * * @author Jean Cyr * @author Thomas Gubler */ #include "geofence.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #define GEOFENCE_OFF 0 #define GEOFENCE_FILE_ONLY 1 #define GEOFENCE_MAX_DISTANCES_ONLY 2 #define GEOFENCE_FILE_AND_MAX_DISTANCES 3 #define GEOFENCE_RANGE_WARNING_LIMIT 3000000 /* Oddly, ERROR is not defined for C++ */ #ifdef ERROR # undef ERROR #endif static const int ERROR = -1; Geofence::Geofence() : SuperBlock(NULL, "GF"), _fence_pub(-1), _home_pos{}, _home_pos_set(false), _last_horizontal_range_warning(0), _last_vertical_range_warning(0), _altitude_min(0), _altitude_max(0), _verticesCount(0), _param_geofence_mode(this, "MODE"), _param_altitude_mode(this, "ALTMODE"), _param_source(this, "SOURCE"), _param_counter_threshold(this, "COUNT"), _param_max_hor_distance(this, "MAX_HOR_DIST"), _param_max_ver_distance(this, "MAX_VER_DIST"), _outside_counter(0), _mavlinkFd(-1) { /* Load initial params */ updateParams(); } Geofence::~Geofence() { } bool Geofence::inside(const struct vehicle_global_position_s &global_position) { return inside(global_position.lat, global_position.lon, global_position.alt); } bool Geofence::inside(const struct vehicle_global_position_s &global_position, float baro_altitude_amsl) { return inside(global_position.lat, global_position.lon, baro_altitude_amsl); } bool Geofence::inside(const struct vehicle_global_position_s &global_position, const struct vehicle_gps_position_s &gps_position, float baro_altitude_amsl, const struct home_position_s home_pos, bool home_position_set) { updateParams(); _home_pos = home_pos; _home_pos_set = home_position_set; if (getAltitudeMode() == Geofence::GF_ALT_MODE_WGS84) { if (getSource() == Geofence::GF_SOURCE_GLOBALPOS) { return inside(global_position); } else { return inside((double)gps_position.lat * 1.0e-7, (double)gps_position.lon * 1.0e-7, (double)gps_position.alt * 1.0e-3); } } else { if (getSource() == Geofence::GF_SOURCE_GLOBALPOS) { return inside(global_position, baro_altitude_amsl); } else { return inside((double)gps_position.lat * 1.0e-7, (double)gps_position.lon * 1.0e-7, baro_altitude_amsl); } } } bool Geofence::inside(double lat, double lon, float altitude) { if (_param_geofence_mode.get() >= GEOFENCE_MAX_DISTANCES_ONLY) { int32_t max_horizontal_distance = _param_max_hor_distance.get(); int32_t max_vertical_distance = _param_max_ver_distance.get(); if (max_horizontal_distance > 0 || max_vertical_distance > 0) { if (_home_pos_set) { float dist_xy = -1.0f; float dist_z = -1.0f; get_distance_to_point_global_wgs84(lat, lon, altitude, _home_pos.lat, _home_pos.lon, _home_pos.alt, &dist_xy, &dist_z); if (max_vertical_distance > 0 && (dist_z > max_vertical_distance)) { if (hrt_elapsed_time(&_last_vertical_range_warning) > GEOFENCE_RANGE_WARNING_LIMIT) { mavlink_log_critical(_mavlinkFd, "Geofence exceeded max vertical distance by %.1f m", (double)(dist_z - max_vertical_distance)); _last_vertical_range_warning = hrt_absolute_time(); } return false; } if (max_horizontal_distance > 0 && (dist_xy > max_horizontal_distance)) { if (hrt_elapsed_time(&_last_horizontal_range_warning) > GEOFENCE_RANGE_WARNING_LIMIT) { mavlink_log_critical(_mavlinkFd, "Geofence exceeded max horizontal distance by %.1f m", (double)(dist_xy - max_horizontal_distance)); _last_horizontal_range_warning = hrt_absolute_time(); } return false; } } } } bool inside_fence = inside_polygon(lat, lon, altitude); if (inside_fence) { _outside_counter = 0; return inside_fence; } else { _outside_counter++; if (_outside_counter > _param_counter_threshold.get()) { return inside_fence; } else { return true; } } } bool Geofence::inside_polygon(double lat, double lon, float altitude) { /* Return true if geofence is disabled or only checking max distances */ if ((_param_geofence_mode.get() == GEOFENCE_OFF) || (_param_geofence_mode.get() == GEOFENCE_MAX_DISTANCES_ONLY)) { return true; } if (valid()) { if (!isEmpty()) { /* Vertical check */ if (altitude > _altitude_max || altitude < _altitude_min) { return false; } /*Horizontal check */ /* Adaptation of algorithm originally presented as * PNPOLY - Point Inclusion in Polygon Test * W. Randolph Franklin (WRF) */ bool c = false; struct fence_vertex_s temp_vertex_i; struct fence_vertex_s temp_vertex_j; /* Red until fence is finished */ for (unsigned i = 0, j = _verticesCount - 1; i < _verticesCount; j = i++) { if (dm_read(DM_KEY_FENCE_POINTS, i, &temp_vertex_i, sizeof(struct fence_vertex_s)) != sizeof(struct fence_vertex_s)) { break; } if (dm_read(DM_KEY_FENCE_POINTS, j, &temp_vertex_j, sizeof(struct fence_vertex_s)) != sizeof(struct fence_vertex_s)) { break; } // skip vertex 0 (return point) if (((double)temp_vertex_i.lon >= lon) != ((double)temp_vertex_j.lon >= lon) && (lat <= (double)(temp_vertex_j.lat - temp_vertex_i.lat) * (lon - (double)temp_vertex_i.lon) / (double)(temp_vertex_j.lon - temp_vertex_i.lon) + (double)temp_vertex_i.lat)) { c = !c; } } return c; } else { /* Empty fence --> accept all points */ return true; } } else { /* Invalid fence --> accept all points */ return true; } } bool Geofence::valid() { // NULL fence is valid if (isEmpty()) { return true; } // Otherwise if ((_verticesCount < 4) || (_verticesCount > GEOFENCE_MAX_VERTICES)) { warnx("Fence must have at least 3 sides and not more than %d", GEOFENCE_MAX_VERTICES - 1); return false; } return true; } void Geofence::addPoint(int argc, char *argv[]) { int ix, last; double lon, lat; struct fence_vertex_s vertex; char *end; if ((argc == 1) && (strcmp("-clear", argv[0]) == 0)) { dm_clear(DM_KEY_FENCE_POINTS); publishFence(0); return; } if (argc < 3) { errx(1, "Specify: -clear | sequence latitude longitude [-publish]"); } ix = atoi(argv[0]); if (ix >= DM_KEY_FENCE_POINTS_MAX) { errx(1, "Sequence must be less than %d", DM_KEY_FENCE_POINTS_MAX); } lat = strtod(argv[1], &end); lon = strtod(argv[2], &end); last = 0; if ((argc > 3) && (strcmp(argv[3], "-publish") == 0)) { last = 1; } vertex.lat = (float)lat; vertex.lon = (float)lon; if (dm_write(DM_KEY_FENCE_POINTS, ix, DM_PERSIST_POWER_ON_RESET, &vertex, sizeof(vertex)) == sizeof(vertex)) { if (last) { publishFence((unsigned)ix + 1); } return; } errx(1, "can't store fence point"); } void Geofence::publishFence(unsigned vertices) { if (_fence_pub == -1) { _fence_pub = orb_advertise(ORB_ID(fence), &vertices); } else { orb_publish(ORB_ID(fence), _fence_pub, &vertices); } } int Geofence::loadFromFile(const char *filename) { FILE *fp; char line[120]; int pointCounter = 0; bool gotVertical = false; const char commentChar = '#'; int rc = ERROR; /* Make sure no data is left in the datamanager */ clearDm(); /* open the mixer definition file */ fp = fopen(GEOFENCE_FILENAME, "r"); if (fp == NULL) { return ERROR; } /* create geofence points from valid lines and store in DM */ for (;;) { /* get a line, bail on error/EOF */ if (fgets(line, sizeof(line), fp) == NULL) { break; } /* Trim leading whitespace */ size_t textStart = 0; while ((textStart < sizeof(line) / sizeof(char)) && isspace(line[textStart])) { textStart++; } /* if the line starts with #, skip */ if (line[textStart] == commentChar) { continue; } /* if there is only a linefeed, skip it */ if (line[0] == '\n') { continue; } if (gotVertical) { /* Parse the line as a geofence point */ struct fence_vertex_s vertex; /* if the line starts with DMS, this means that the coordinate is given as degree minute second instead of decimal degrees */ if (line[textStart] == 'D' && line[textStart + 1] == 'M' && line[textStart + 2] == 'S') { /* Handle degree minute second format */ float lat_d, lat_m, lat_s, lon_d, lon_m, lon_s; if (sscanf(line, "DMS %f %f %f %f %f %f", &lat_d, &lat_m, &lat_s, &lon_d, &lon_m, &lon_s) != 6) { warnx("Scanf to parse DMS geofence vertex failed."); goto error; } // warnx("Geofence DMS: %.5f %.5f %.5f ; %.5f %.5f %.5f", (double)lat_d, (double)lat_m, (double)lat_s, (double)lon_d, (double)lon_m, (double)lon_s); vertex.lat = lat_d + lat_m / 60.0f + lat_s / 3600.0f; vertex.lon = lon_d + lon_m / 60.0f + lon_s / 3600.0f; } else { /* Handle decimal degree format */ if (sscanf(line, "%f %f", &(vertex.lat), &(vertex.lon)) != 2) { warnx("Scanf to parse geofence vertex failed."); goto error; } } if (dm_write(DM_KEY_FENCE_POINTS, pointCounter, DM_PERSIST_POWER_ON_RESET, &vertex, sizeof(vertex)) != sizeof(vertex)) { goto error; } warnx("Geofence: point: %d, lat %.5f: lon: %.5f", pointCounter, (double)vertex.lat, (double)vertex.lon); pointCounter++; } else { /* Parse the line as the vertical limits */ if (sscanf(line, "%f %f", &_altitude_min, &_altitude_max) != 2) { goto error; } warnx("Geofence: alt min: %.4f, alt_max: %.4f", (double)_altitude_min, (double)_altitude_max); gotVertical = true; } } /* Check if import was successful */ if (gotVertical && pointCounter > 0) { _verticesCount = pointCounter; warnx("Geofence: imported successfully"); mavlink_log_info(_mavlinkFd, "Geofence imported"); rc = OK; } else { warnx("Geofence: import error"); mavlink_log_critical(_mavlinkFd, "Geofence import error"); } error: fclose(fp); return rc; } int Geofence::clearDm() { dm_clear(DM_KEY_FENCE_POINTS); return OK; }