Skip to content
Snippets Groups Projects
Commit 5d23921f authored by Lorenz Gruber's avatar Lorenz Gruber
Browse files

adapting neighbours and contraction task for coordinate-system-sensitive distance predicates

parent 740f8d7c
Branches
No related tags found
No related merge requests found
......@@ -8,11 +8,9 @@
using json = nlohmann::json;
/**
* @brief Neighbours Config Parser
* @brief Neighbours Config
*
* @tparam GeometryType type of the settlement geometry (e.g. Polygon)
*/
template<typename GeometryType>
struct FindNeighboursConfig: public MemgraphTaskConfig{
constexpr static const char * MAX_DISTANCE_KEY = "maxDistanceMeters";
constexpr static const char * NEIGHBOURING_PREDICATES_KEY = "neighbouring-predicates";
......@@ -20,23 +18,32 @@ struct FindNeighboursConfig: public MemgraphTaskConfig{
double maxEdgeDistance;
size_t maxNeighbours;
std::vector<fishnet::util::BiPredicate_t<GeometryType>> neighbouringPredicates;
FindNeighboursConfig()=default;
FindNeighboursConfig(const json & configDescription):MemgraphTaskConfig(configDescription){
jsonDescription.at(MAX_DISTANCE_KEY).get_to(this->maxEdgeDistance);
jsonDescription.at(MAX_NEIGHBOURS_KEY).get_to(this->maxNeighbours);
this->neighbouringPredicates.push_back(DistanceBiPredicate(this->maxEdgeDistance));
}
/**
* @brief
*
* @tparam GeometryType type of the settlement geometry (e.g. Polygon)
*/
template<typename GeometryType>
std::vector<fishnet::util::BiPredicate_t<GeometryType>> initNeighbouringPredicates(){
std::vector<fishnet::util::BiPredicate_t<GeometryType>> neighbouringPredicates;
for(const auto & neighbourPredicateJson : jsonDescription.at(NEIGHBOURING_PREDICATES_KEY)){
std::string predicateName;
neighbourPredicateJson.at("name").get_to(predicateName);
auto neighbourPredicate = magic_enum::enum_cast<NeighbouringPredicateType>(predicateName)
.and_then([&neighbourPredicateJson](NeighbouringPredicateType type){return fromNeighbouringPredicateType<GeometryType>(type,neighbourPredicateJson);});
if(not neighbourPredicate) {
throw std::runtime_error("Could not parse json to Neighbouring BiPredicate:\n"+neighbourPredicateJson.dump()+"\nFilter name might not \""+predicateName+"\" be supported");
throw std::runtime_error("Could not parse json to Neighbouring BiPredicate:\n"+neighbourPredicateJson.dump()+"\nPredicate name \""+predicateName+"\" not supported");
}
this->neighbouringPredicates.push_back(std::move(neighbourPredicate.value()));
neighbouringPredicates.push_back(std::move(neighbourPredicate.value()));
}
return neighbouringPredicates;
}
};
\ No newline at end of file
......@@ -22,7 +22,7 @@ int main(int argc, char const *argv[]){
});
app.add_option("-c,--config",configFilename,"Path to configuration file")->required()->check(CLI::ExistingFile);
CLI11_PARSE(app,argc,argv);
FindNeighboursTask<GeometryType> task {FindNeighboursConfig<GeometryType>(json::parse(std::ifstream(configFilename))),fishnet::Shapefile(primaryInput),workflowID};
FindNeighboursTask<GeometryType> task {FindNeighboursConfig(json::parse(std::ifstream(configFilename))),fishnet::Shapefile(primaryInput),workflowID};
for(auto && filename : additionalInputs) {
task.addShapefile(fishnet::Shapefile(filename));
}
......
......@@ -21,9 +21,10 @@
template<fishnet::geometry::IPolygon P>
class FindNeighboursTask : public Task{
private:
FindNeighboursConfig<P> config;
FindNeighboursConfig config;
fishnet::Shapefile primaryInput;
std::vector<fishnet::Shapefile> additionalInput;
DistanceFunction distanceFunction;
using number = typename P::numeric_type;
struct PrimaryInputAABB{
......@@ -50,7 +51,7 @@ private:
};
public:
FindNeighboursTask(FindNeighboursConfig<P> && config,fishnet::Shapefile primaryInput, size_t workflowID):Task(workflowID),config(std::move(config)),primaryInput(std::move(primaryInput)){
FindNeighboursTask(FindNeighboursConfig && config,fishnet::Shapefile primaryInput, size_t workflowID):Task(workflowID),config(std::move(config)),primaryInput(std::move(primaryInput)){
this->desc["type"]="NEIGHBOURS";
this->desc["config"]=this->config.jsonDescription;
this->desc["primary-input"] = this->primaryInput.getPath().filename().string();
......@@ -61,15 +62,10 @@ public:
return *this;
}
template<fishnet::util::BiPredicate<P> NeighbourBiPredicate>
FindNeighboursTask<P> & addNeighbouringPredicate(NeighbourBiPredicate && predicate) noexcept {
config.neighbouringPredicates.push_back(std::forward<NeighbourBiPredicate>(predicate));
return *this;
}
std::vector<SettlementPolygon<P>> readInput(auto const & graph) {
std::vector<SettlementPolygon<P>> polygons;
auto layer = fishnet::VectorLayer<P>::read(primaryInput); // load polygons from primary shapefile
distanceFunction = distanceFunctionForSpatialReference(layer.getSpatialReference());
if(layer.isEmpty()){
return polygons;
}
......@@ -90,13 +86,15 @@ public:
inputBoundingBox.update(feature.getGeometry());
polygons.emplace_back(optId.value(),primaryFileRef.value(),std::move(feature.getGeometry())); // create settlement wrapper containing its unique id and geometry
}
DistanceBiPredicate distanceToPrimaryInput {config.maxEdgeDistance};
DistanceBiPredicate distanceToPrimaryInput {distanceFunction,config.maxEdgeDistance};
fishnet::geometry::Rectangle<number> primaryInputAABB = inputBoundingBox.asShape();
std::vector<std::string> additionalInputStrings;
std::ranges::for_each(additionalInput,[&additionalInputStrings](auto const & file){additionalInputStrings.push_back(file.getPath().filename().string());});
this->desc["additional-inputs"]=additionalInputStrings;
for(const auto & shp : additionalInput) {
auto neighbourLayer = fishnet::VectorLayer<P>::read(shp); // load polygons from shapefile
if(not layer.getSpatialReference().IsSame(&neighbourLayer.getSpatialReference()))
throw std::runtime_error("Spatial reference of neighbouring file does not match!\nExpecting: "+std::string(layer.getSpatialReference().GetName())+"\nActual: "+neighbourLayer.getSpatialReference().GetName());
if(neighbourLayer.isEmpty())
continue;
auto fileRef = graph.getAdjacencyContainer().getDatabaseConnection().addFileReference(shp.getPath()); // load file reference from database
......@@ -120,21 +118,22 @@ public:
}
void run() override{
fishnet::util::AllOfPredicate<P,P> neighbouringPredicate;
/* add all neighbouring predicates to composite predicate */
std::ranges::for_each(config.neighbouringPredicates,[&neighbouringPredicate](const auto & predicate){neighbouringPredicate.add(predicate);});
auto graph = fishnet::graph::GraphFactory::UndirectedGraph<SettlementPolygon<P>>(
MemgraphAdjacency<SettlementPolygon<P>>(MemgraphClient(MemgraphConnection::create(config.params,workflowID).value_or_throw()))
);
std::vector<SettlementPolygon<P>> polygons = readInput(graph);
double maxEdgeDistanceVar = config.maxEdgeDistance;
auto boundingBoxPolygonWrapper = [maxEdgeDistanceVar](const SettlementPolygon<P> & settPolygon ){
auto boundingBoxPolygonWrapper = [maxEdgeDistanceVar,this](const SettlementPolygon<P> & settPolygon ){
/* Create scaled aaBB containing at least all points reachable from the polygon within the maximum edge distance*/
auto aaBB = fishnet::geometry::Rectangle<fishnet::math::DEFAULT_NUMERIC>(settPolygon);
double distanceMetersTopLeftBotLeft = fishnet::WGS84Ellipsoid::distance(aaBB.left(),aaBB.top(),aaBB.left(),aaBB.bottom());
double distanceMetersTopLeftBotLeft = distanceFunction({aaBB.left(),aaBB.top()},{aaBB.left(),aaBB.bottom()});
double scale = (maxEdgeDistanceVar / distanceMetersTopLeftBotLeft) +1;
return fishnet::geometry::BoundingBoxPolygon(settPolygon,aaBB.scale(scale));
};
fishnet::util::AllOfPredicate<P,P> neighbouringPredicate;
/* add all neighbouring predicates to composite predicate */
neighbouringPredicate.add(DistanceBiPredicate(distanceFunction,config.maxEdgeDistance));
std::ranges::for_each(config.initNeighbouringPredicates<P>(),[&neighbouringPredicate](const auto & predicate){neighbouringPredicate.add(predicate);});
auto shortCircuitPredicate = [neighbouringPredicate= std::move(neighbouringPredicate)](const fishnet::geometry::BoundingBoxPolygon<SettlementPolygon<P>> & lhs, const fishnet::geometry::BoundingBoxPolygon<SettlementPolygon<P>> & rhs){
return lhs.getBoundingBox().overlap(rhs.getBoundingBox()) && neighbouringPredicate(lhs.getPolygon(),rhs.getPolygon());
};
......
......@@ -9,27 +9,30 @@
/**
* @brief Contraction configuration parser
*
* @tparam GeometryType
*/
template<typename GeometryType>
struct ContractionConfig:public MemgraphTaskConfig{
constexpr static const char * CONTRACTION_PREDICATES_KEY = "contraction-predicates";
constexpr static const char * WORKERS_KEY = "merge-workers";
std::vector<fishnet::util::BiPredicate_t<GeometryType>> contractBiPredicates;
u_int8_t workers;
ContractionConfig(const json & config):MemgraphTaskConfig(config){
this->jsonDescription.at(WORKERS_KEY).get_to(this->workers);
}
template<typename GeometryType>
std::vector<fishnet::util::BiPredicate_t<GeometryType>> initContractionPredicates(DistanceFunction const & distanceFunction) {
std::vector<fishnet::util::BiPredicate_t<GeometryType>> contractBiPredicates;
for(const auto & contractPredicateJson : jsonDescription.at(CONTRACTION_PREDICATES_KEY)){
std::string predicateName;
contractPredicateJson.at("name").get_to(predicateName);
auto contractionPredicate = magic_enum::enum_cast<NeighbouringPredicateType>(predicateName)
.and_then([&contractPredicateJson](NeighbouringPredicateType type){return fromNeighbouringPredicateType<GeometryType>(type,contractPredicateJson);});
.and_then([&contractPredicateJson,&distanceFunction](NeighbouringPredicateType type){return fromNeighbouringPredicateType<GeometryType>(type,contractPredicateJson,distanceFunction);});
if(not contractionPredicate) {
throw std::runtime_error("Could not parse json to Neighbouring BiPredicate:\n"+contractPredicateJson.dump()+"\nFilter name might not \""+predicateName+"\" be supported");
}
this->contractBiPredicates.push_back(std::move(contractionPredicate.value()));
contractBiPredicates.push_back(std::move(contractionPredicate.value()));
}
return contractBiPredicates;
}
};
\ No newline at end of file
......@@ -37,7 +37,7 @@ int main(int argc, const char * argv[]){
CLI11_PARSE(app,argc,argv);
if(inputFilenames.size() < 1)
throw std::runtime_error("No input files provided");
ContractionConfig<GeometryType> config {json::parse(std::ifstream(configFilename))};
ContractionConfig config {json::parse(std::ifstream(configFilename))};
auto outputPath = std::filesystem::path(outputDirectory) / std::filesystem::path(outputStem+".shp");
fishnet::Shapefile output {outputPath};
ContractionTask<GeometryType> task {std::move(config),std::move(components),output,workflowID};
......
......@@ -24,7 +24,7 @@ class ContractionTask:public Task{
private:
std::vector<fishnet::Shapefile> inputs;
std::vector<ComponentReference> components;
ContractionConfig<P> config;
ContractionConfig config;
fishnet::Shapefile output;
public:
/**
......@@ -39,7 +39,7 @@ public:
* @brief settlement type after the contraction
*/
using ResultNodeType = SettlementPolygon<ResultGeometryType>;
ContractionTask(ContractionConfig<P> && config,std::vector<ComponentReference> && components,fishnet::Shapefile output,size_t workflowID):Task(workflowID),components(std::move(components)),config(std::move(config)),output(std::move(output)){
ContractionTask(ContractionConfig && config,std::vector<ComponentReference> && components,fishnet::Shapefile output,size_t workflowID):Task(workflowID),components(std::move(components)),config(std::move(config)),output(std::move(output)){
this->desc["type"]="CONTRACTION";
this->desc["config"]=this->config.jsonDescription;
std::vector<std::string> componentStrings;
......@@ -68,6 +68,10 @@ public:
this->desc["inputs"]=inputStrings;
for(const auto & shp : inputs) {
auto layer = fishnet::VectorLayer<P>::read(shp);
if(spatialRef.IsEmpty())
spatialRef = layer.getSpatialReference();
if(not spatialRef.IsSame(&layer.getSpatialReference()))
throw std::runtime_error("Spatial reference of files do not match!\nExpecting: "+std::string(spatialRef.GetName())+"\nActual: "+layer.getSpatialReference().GetName());
if(layer.isEmpty())
continue;
auto fileRef = adj.getDatabaseConnection().addFileReference(shp.getPath());
......@@ -75,7 +79,6 @@ public:
throw std::runtime_error("Could not read file reference for shp file:\n"+shp.getPath().string());
}
auto optFishnetIdField = layer.getSizeField(Task::FISHNET_ID_FIELD);
spatialRef = layer.getSpatialReference();
if(not optFishnetIdField) {
throw std::runtime_error("Could not find FISHNET_ID field in shp file: \n"+shp.getPath().string());
}
......@@ -112,8 +115,8 @@ public:
auto reduceFunction = IDReduceFunction(outputFileRef.value());
auto contractionPredicate = fishnet::util::AllOfPredicate<SourceNodeType,SourceNodeType>();
/* Load all contraction predicates into a single composite contraction predicate */
std::ranges::for_each(config.contractBiPredicates,[&contractionPredicate](const auto & predicate){contractionPredicate.add(
[&predicate](const SourceNodeType & lhs, const SourceNodeType & rhs){return predicate(static_cast<P>(lhs),static_cast<P>(rhs));});
std::ranges::for_each(config.initContractionPredicates<P>(distanceFunctionForSpatialReference(ref)),[&contractionPredicate](auto && p){contractionPredicate.add(
[predicate=std::move(p)](const SourceNodeType & lhs, const SourceNodeType & rhs){return predicate(static_cast<P>(lhs),static_cast<P>(rhs));});
});
/* Contract the graph according to the composite contraction predicate. Done in place, to remove old adjacencies from the database as well to allow the reuse of ids, while remaining consistency*/
fishnet::graph::contractInPlace(sourceGraph,contractionPredicate,reduceFunction,resultGraph,config.workers);
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment