diff --git a/examples/Advanced/GeoZones/GeoZones.cpp b/examples/Advanced/GeoZones/GeoZones.cpp new file mode 100644 index 00000000..75a1be55 --- /dev/null +++ b/examples/Advanced/GeoZones/GeoZones.cpp @@ -0,0 +1,187 @@ +#include "GeoZones.hpp" + +#include +#include +#include +#include +#include +#include + +#include +namespace co +{ +static GeoZones::result +pip(const GeoZones::zone& z, double latitude, double longitude, double blur) +{ + auto pt = GeoZones::geom_point{latitude, longitude}; + bool within = boost::geometry::within(pt, z.polygon); + double distance_side = std::abs(boost::geometry::distance(pt, z.polygon)); + double distance_center = std::abs(boost::geometry::distance(pt, z.center)); + double influence + = within ? 1.f + : std::clamp( + std::pow(1. / (1.0 + distance_center), 1e5 * (1. - blur)), 0., 1.); + + return GeoZones::result{ + .to_side = (float)distance_side, + .to_center = (float)distance_center, + .influence = (float)influence}; +} + +void GeoZones::operator()() +{ + m_outputs.inside = {}; + m_outputs.closest = {}; + m_outputs.attributes.clear(); + m_outputs.per_zone.clear(); + + if(m_zones.empty()) + { + return; + } + + auto in_fence = -1; + auto closest_fence = -1; + + double min_distance = 1e100; + m_outputs.per_zone.clear(); + m_outputs.per_zone.resize(m_zones.size()); + const auto [lat, lon] = [&]() -> std::pair { + if(inputs.normalize) + { + const double w = m_bounding1.x() - m_bounding0.x(); + const double h = m_bounding1.y() - m_bounding0.y(); + const double lat = inputs.latitude * w + m_bounding0.x(); + const double lon = inputs.longitude * h + m_bounding0.y(); + return {lat, lon}; + } + else + { + return {inputs.latitude, inputs.longitude}; + } + }(); + for(int i = 0, N = m_zones.size(); i < N; i++) + { + auto& z = m_outputs.per_zone[i]; + z = pip(m_zones[i], lat, lon, inputs.blur); + if(z.influence >= 1.) + { + in_fence = i; + } + + if(z.to_center < min_distance) + { + closest_fence = i; + min_distance = z.to_center; + } + } + + if(closest_fence >= 0) + m_outputs.closest = m_outputs.per_zone[closest_fence]; + else + m_outputs.closest = {}; + if(in_fence >= 0) + m_outputs.inside = m_outputs.per_zone[in_fence]; + else + m_outputs.inside = {}; + + for(const auto& k : m_attributes) + m_outputs.attributes[k] = 0.; + if(in_fence != -1) + { + for(const auto& [k, v] : m_zones[in_fence].attributes) + m_outputs.attributes[k] = v; + } + else + { + for(int i = 0, N = m_zones.size(); i < N; i++) + { + for(const auto& [k, v] : m_zones[i].attributes) + m_outputs.attributes[k] += v * m_outputs.per_zone[i].influence; + } + // for(const auto& [k, v] : m_outputs.attributes) + // { + // m_outputs.attributes[k] += v * m_outputs.per_zone[i].influence; + // } + } + + outputs.zones.value = oscr::to_ossia_value(m_outputs); +} + +void GeoZones::loadZones() +{ + m_attributes.clear(); + m_bounding0 = {}; + m_bounding1 = {}; + m_zones.clear(); + + try + { + rapidjson::Document doc; + doc.Parse(inputs.zones.value); + if(doc.HasParseError()) + return; + if(!doc.IsArray()) + return; + + boost::geometry::model::multi_polygon polys; + for(auto& obj : doc.GetArray()) + { + zone z; + + if(!obj.IsObject()) + continue; + + auto positions_it = obj.FindMember("polygon"); + if(positions_it == obj.MemberEnd()) + continue; + if(!positions_it->value.IsArray()) + continue; + + for(const auto& pos : positions_it->value.GetArray()) + { + if(pos[0].IsNumber() && pos[1].IsNumber()) + { + z.positions.emplace_back(pos[0].GetDouble(), pos[1].GetDouble()); + } + } + boost::geometry::assign_points(z.polygon, z.positions); + polys.push_back(z.polygon); + z.center = boost::geometry::return_centroid(z.polygon); + + for(const auto& mem : obj.GetObject()) + { + if(mem.value.GetType() == rapidjson::kNumberType) + { + m_attributes.insert(mem.name.GetString()); + + z.attributes[mem.name.GetString()] = mem.value.GetDouble(); + } + else if(mem.value.GetType() == rapidjson::kStringType) + { + auto enum_str = mem.value.GetString(); + std::vector result; + boost::split(result, enum_str, boost::is_any_of("|")); + + for(auto& an_enum : result) + { + auto str = fmt::format("{}_{}", mem.name.GetString(), an_enum); + m_attributes.insert(str); + + z.attributes[str] = 1.; + } + } + } + m_zones.push_back(std::move(z)); + } + + boost::geometry::model::box box; + boost::geometry::envelope(polys, box); + m_bounding0 = box.min_corner(); + m_bounding1 = box.max_corner(); + } + catch(...) + { + } +} +} diff --git a/examples/Advanced/GeoZones/GeoZones.hpp b/examples/Advanced/GeoZones/GeoZones.hpp new file mode 100644 index 00000000..259696cf --- /dev/null +++ b/examples/Advanced/GeoZones/GeoZones.hpp @@ -0,0 +1,71 @@ +#pragma once +#include +#include +#include +#include +#include + +namespace co +{ +struct GeoZones +{ + halp_meta(name, "Geo Zones") + halp_meta(category, "Spatial") + halp_meta(c_name, "geozones") + halp_meta(author, "Jean-Michaƫl Celerier, Brice Ammar-Khodja") + halp_meta(uuid, "b5690418-5832-4038-9549-5cc69b77008c") + + struct + { + struct : halp::lineedit<"Program", ""> + { + halp_meta(language, "json") + void update(GeoZones& self) { self.loadZones(); } + } zones; + + halp::hslider_f32<"Latitude", halp::range{0, 1, 0.5}> latitude; + halp::hslider_f32<"Longitude", halp::range{0, 1, 0.5}> longitude; + halp::toggle<"Normalize"> normalize; + halp::knob_f32<"Blur", halp::range{0, 1, 0.5}> blur; + } inputs; + + struct + { + halp::val_port<"Out", ossia::value> zones; + } outputs; + + void operator()(); + void loadZones(); + + using geom_point = boost::geometry::model::d2::point_xy; + using geom_polygon = boost::geometry::model::polygon; + struct zone + { + std::vector positions; + geom_polygon polygon; + geom_point center{}; + boost::container::flat_map attributes; + }; + + struct result + { + float to_side; + float to_center; + float influence; + }; + + struct output + { + halp_field_names(inside, closest, per_zone, attributes); + result inside; + result closest; + std::vector per_zone; + boost::container::flat_map attributes; + }; + + geom_point m_bounding0, m_bounding1; + ossia::flat_set m_attributes; + std::vector m_zones; + output m_outputs; +}; +}