-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added MigrantMapper, computing the probability of an agent being a mi…
…grant
- Loading branch information
1 parent
35a929a
commit 5883ea7
Showing
2 changed files
with
381 additions
and
0 deletions.
There are no files selected for viewing
108 changes: 108 additions & 0 deletions
108
src/main/java/org/matsim/prepare/HomeMultipleLocationFilter.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
package org.matsim.prepare; | ||
|
||
|
||
import org.matsim.api.core.v01.Coord; | ||
import org.matsim.api.core.v01.Id; | ||
import org.matsim.api.core.v01.population.Activity; | ||
import org.matsim.api.core.v01.population.Person; | ||
import org.matsim.api.core.v01.population.PlanElement; | ||
import org.matsim.api.core.v01.population.Population; | ||
import org.matsim.application.options.ShpOptions; | ||
|
||
import java.util.*; | ||
|
||
/** | ||
* Same use as {@link org.matsim.application.analysis.HomeLocationFilter} but this class also differs between different polygons in the shapefile. | ||
*/ | ||
public class HomeMultipleLocationFilter { | ||
private Map<String, Set<Id<Person>>> personMapping = new HashMap<>(); | ||
private ShpOptions.Index index; | ||
|
||
/** | ||
* | ||
* @param analysisAreaShapeFile .shp-file | ||
* @param inputCRS Coordinate Reference System as String (e.g. "EPSG:25832") | ||
* @param columnToMap polygon-attribute that should be used as key of the population mapping. | ||
* If two different polygons have the same value in this attribute, the population inside those | ||
* polygons will be mapped as one category. | ||
* @param population | ||
*/ | ||
public HomeMultipleLocationFilter(ShpOptions analysisAreaShapeFile, String inputCRS, String columnToMap, Population population){ | ||
//Create an index, searching for the given attribute | ||
index = analysisAreaShapeFile.createIndex(inputCRS, columnToMap); | ||
|
||
//Map the population | ||
for(Person p : population.getPersons().values()){ | ||
List<PlanElement> planElements = p.getSelectedPlan().getPlanElements(); // Get the selected plan of Person | ||
for(PlanElement el : planElements){ | ||
if (el instanceof Activity && ((Activity)el).getType().startsWith("home")) { //Find his home activity | ||
Coord coord = ((Activity)el).getCoord(); | ||
if(index.contains(coord)){ //Check if home lies inside our shapefile | ||
personMapping.putIfAbsent(index.query(coord), new HashSet<>()); //Init Set if null | ||
personMapping.get(index.query(coord)).add(p.getId()); //Add person to this mapping-category | ||
break; | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* | ||
* @param analysisAreaShapeFile .shp-file | ||
* @param inputCRS Coordinate Reference System as String (e.g. "EPSG:25832") | ||
* @param columnToMap polygon-attribute that should be used as key of the population mapping. | ||
* If two different polygons have the same value in this attribute, the population inside those | ||
* polygons will be mapped as one category. | ||
* @param priorityList A List of sets, that contains String with keys. First set has highest priority, second set has second-highest priority, ... | ||
* Elements that are in no set at all have the lowest priority. Only important if agents can be in mutliple shapes (e.g. if they are overlapping) | ||
* @param population | ||
*/ | ||
public HomeMultipleLocationFilter(ShpOptions analysisAreaShapeFile, String inputCRS, String columnToMap, List<Set<String>> priorityList, Population population){ | ||
//TODO | ||
//Create an index, searching for the given attribute | ||
index = analysisAreaShapeFile.createIndex(inputCRS, columnToMap); | ||
|
||
//Map the population | ||
for(Person p : population.getPersons().values()){ | ||
List<PlanElement> planElements = p.getSelectedPlan().getPlanElements(); // Get the selected plan of Person | ||
for(PlanElement el : planElements){ | ||
if (el instanceof Activity && ((Activity)el).getType().startsWith("home")) { //Find his home activity | ||
Coord coord = ((Activity)el).getCoord(); | ||
if(index.contains(coord)){ //Check if home lies inside our shapefile | ||
personMapping.putIfAbsent(index.query(coord), new HashSet<>()); //Init Set if null | ||
personMapping.get(index.query(coord)).add(p.getId()); //Add person to this mapping-category | ||
break; | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
|
||
/** | ||
* Checks if the given person lives in the specific polygon-category of the shapefile given by the {@code key}. | ||
* @param p Person to check | ||
* @param key Will search in all polygons, that have this {@code key} value in the {@code columnToMap} attribute. | ||
*/ | ||
public boolean checkIfPersonInPolygon(Person p, String key){ // TODO Replace by efficient solution | ||
return personMapping.get(key).contains(p.getId()); | ||
} | ||
|
||
/** | ||
* Gets the category-key in which this person lives. | ||
* @param p Person to check | ||
* @return String of category key. Returns {@code null} if person does not live in any category | ||
*/ | ||
public String getCategoryKeyOfPerson(Person p){ | ||
//TODO Fix for person that are in multiple overlapping shapefiles | ||
for(var e : personMapping.entrySet()){ | ||
if(e.getValue().contains(p.getId())) return e.getKey(); | ||
} | ||
return null; | ||
} | ||
|
||
public int occurrencesOfKey(String key){ | ||
return personMapping.get(key).size(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,273 @@ | ||
package org.matsim.prepare; | ||
|
||
import org.matsim.api.core.v01.Coord; | ||
import org.matsim.api.core.v01.Id; | ||
import org.matsim.api.core.v01.population.Activity; | ||
import org.matsim.api.core.v01.population.Person; | ||
import org.matsim.api.core.v01.population.PlanElement; | ||
import org.matsim.api.core.v01.population.Population; | ||
import org.matsim.application.options.ShpOptions; | ||
import org.matsim.core.network.NetworkUtils; | ||
import org.matsim.core.population.PopulationUtils; | ||
|
||
import java.nio.charset.StandardCharsets; | ||
import java.nio.file.Path; | ||
import java.util.*; | ||
|
||
/** | ||
* Creates a boolean-map for the population, that shows whether this person is a migrant and puts it into the population. | ||
* Used in GlaMoBi-Project, containing migrant data for Gladbeck. | ||
*/ | ||
public class MigrantMapper { | ||
private int migrants; | ||
private Population pop; | ||
private final HomeMultipleLocationFilter filter; | ||
|
||
private final Map<Id<Person>, Double> migrantProbabilityMap; | ||
|
||
//TODO DEBUG | ||
Map<String, Integer> district_amounts = new HashMap<>(); | ||
Map<String, Integer> district_migrant_amounts = new HashMap<>(); | ||
|
||
/** | ||
* An activity location. | ||
*/ | ||
private class Location{ | ||
String name; | ||
Coord coord; | ||
double migrantProbability; | ||
Location(String name, Coord coord, double migrantProbability){ | ||
this.name = name; | ||
this.coord = coord; | ||
this.migrantProbability = migrantProbability; | ||
} | ||
} | ||
|
||
/** | ||
* Computes the probability of every Person to be a migrant and saves it as a map in {@code migrantProbabilityMap}. | ||
* @param populationPath Path to the population file | ||
* @param cityDistrictSHPPath Path to the shapefile containing the districts of the city | ||
* @param districtIdentifierName Identifying attribute of the polygons of the shapefile (e.g. Name) | ||
* @param CRS Coordinate Reference System (e.g. EPSG:25832) | ||
*/ | ||
public MigrantMapper(String populationPath, String cityDistrictSHPPath, String districtIdentifierName, String CRS){ // TODO custom filepaths | ||
pop = PopulationUtils.readPopulation(populationPath); | ||
ShpOptions shp = new ShpOptions(Path.of(cityDistrictSHPPath), CRS, StandardCharsets.UTF_8); | ||
ShpOptions.Index index = shp.createIndex(CRS, districtIdentifierName); | ||
filter = new HomeMultipleLocationFilter(shp, CRS, districtIdentifierName, pop); | ||
migrantProbabilityMap = new HashMap<>(); | ||
|
||
for(Person p : pop.getPersons().values()){ | ||
migrantProbabilityMap.put(p.getId(), computeRefugeeProbability(p)); | ||
} | ||
|
||
assignMigrantMapToPopulation(); | ||
} | ||
|
||
/** | ||
* @return the probability of the given person to be a migrant | ||
*/ | ||
double computeRefugeeProbability(Person p){ | ||
int age = (int) p.getAttributes().getAttribute("age"); | ||
double income = (double) p.getAttributes().getAttribute("income"); | ||
String gender = (String) p.getAttributes().getAttribute("sex"); | ||
String region = filter.getCategoryKeyOfPerson(p); | ||
|
||
//TODO DEBUG | ||
district_amounts.putIfAbsent(region, 0); | ||
district_amounts.put(region, district_amounts.get(region)+1); | ||
//DEBUG END | ||
|
||
//TODO Initalizer values, in case that the given person-data is | ||
double ageProbability = 0.; //P(M|A,G) Wahrscheinlhckeit, dass p ein Migrant ist unter Beruecksichtigung des Alters und des Geschlechts | ||
double incomeProbability = 0.; //P(M|€) Wahrscheinlichkeit, dass p ein Migrant ist unter Beruecksichtigung des Einkommens | ||
double regionProbability = 0.1885; //P(M|V) Wahrscheinlichkeit, dass p ein Migrant ist unter Beruecksichtigung des Wohnorts TODO | ||
double destProbability = 0.; //P(M|Z) Wahrscheinlichkeit, dass p ein Migrant ist unter Beruecksichtigung der Zielaktivitaeten TODO | ||
|
||
//Age-Gender Probability | ||
if(gender.equals("m")){ | ||
if(age < 18){ | ||
ageProbability = 0.2; | ||
} else if (age < 29){ | ||
ageProbability = 0.2688; | ||
} else if (age < 45){ | ||
ageProbability = 0.2278; | ||
} else if (age < 65){ | ||
ageProbability = 0.126; | ||
} else{ | ||
ageProbability = 0.062; | ||
} | ||
} else if(gender.equals("w")){ | ||
if(age < 18){ | ||
ageProbability = 0.1505; | ||
} else if (age < 29){ | ||
ageProbability = 0.0672; | ||
} else if (age < 45){ | ||
ageProbability = 0.1122; | ||
} else if (age < 65){ | ||
ageProbability = 0.063; | ||
} else{ | ||
ageProbability = 0.0729; | ||
} | ||
} | ||
|
||
//Income Probability | ||
if (income == 0.) { | ||
incomeProbability = 0.4109; | ||
} else if (income < 500){ | ||
incomeProbability = 0.4736; | ||
} else if (income < 1000){ | ||
incomeProbability = 0.3583; | ||
} else if (income < 1500){ | ||
incomeProbability = 0.2535; | ||
} else if (income < 2000){ | ||
incomeProbability = 0.2477; | ||
} else if (income < 2500){ | ||
incomeProbability = 0.2353; | ||
} else if (income < 3000){ | ||
incomeProbability = 0.2274; | ||
} else if (income < 3500){ | ||
incomeProbability = 0.2044; | ||
} else { // income >= 3500 | ||
incomeProbability = 0.1939; | ||
} | ||
|
||
//RegionProbability | ||
if(region != null){ | ||
regionProbability = switch (region) { | ||
case "Übergangsheim" -> 10; //TODO | ||
case "Mitte I" -> 0.2246; | ||
case "Mitte II" -> 0.1460; | ||
case "Zweckel" -> 0.1327; | ||
case "Alt-Rentford" -> 0.0530; | ||
case "Rentford-Nord" -> 0.1515; | ||
case "Schultendorf" -> 0.1101; | ||
case "Ellinghorst" -> 0.1048; | ||
case "Butendorf" -> 0.2180; | ||
case "Brauck" -> 0.2921; | ||
case "Rosenhügel" -> 0.2061; | ||
default -> regionProbability; | ||
}; | ||
} | ||
|
||
//DestinationProbability | ||
destProbability = checkIfActivitiesAreRelevant(p); | ||
|
||
//Probability summary | ||
//TODO Create an actual stochastic procedure | ||
if(destProbability != -1) return (incomeProbability + ageProbability + 3*regionProbability + destProbability) / 6; | ||
return (incomeProbability + ageProbability + 3*regionProbability) / 5; | ||
} | ||
|
||
/** | ||
* CHecks if any of the agents activities is next to an important social facility. | ||
* @return The probability of this agent being a migrant using activities as hints | ||
*/ | ||
private double checkIfActivitiesAreRelevant(Person p){ | ||
//TODO Make this more useful | ||
Location[] locations = new Location[]{ | ||
new Location("Büro für Interkulturelle Arbeit", new Coord(361436.57,5712850.89), 0.5), | ||
new Location("Jugendmigrationsdienst", new Coord(367983.90,5708550.07), 0.5), | ||
new Location("Amt Für Soziales Und Wohnen", new Coord(360574.17,5715060.27), 0.5), | ||
new Location("Jobcenter", new Coord(360610.97,5715105.85), 0.5) | ||
}; | ||
|
||
List<Double> probabilities = new LinkedList<>(); | ||
|
||
for(PlanElement a : p.getSelectedPlan().getPlanElements()){ | ||
if(a instanceof Activity){ | ||
for(Location l : locations){ | ||
if(NetworkUtils.getEuclideanDistance(((Activity) a).getCoord(), l.coord) < 50){ | ||
probabilities.add(l.migrantProbability); | ||
} | ||
} | ||
} | ||
} | ||
|
||
if(probabilities.isEmpty()) return -1; | ||
|
||
double probability = 0.; | ||
for(double prob : probabilities){ | ||
probability += prob; | ||
} | ||
return probability/probabilities.size(); | ||
} | ||
|
||
/** | ||
* Adds a boolean-attribute "isMigrant" to every Person of the given population. | ||
* NOTE: This is a non-deterministic method. It uses the computed probabilities. | ||
* @return the amount of migrants in this population | ||
*/ | ||
private void assignMigrantMapToPopulation(){ | ||
int totalMigrants = 0; | ||
Random rand = new Random(); | ||
for(var e : migrantProbabilityMap.entrySet()){ | ||
double r = e.getValue(); | ||
boolean isMigrant = rand.nextInt(1000) < r*1000; | ||
pop.getPersons().get(e.getKey()).getAttributes().putAttribute("isMigrant", isMigrant); | ||
if (isMigrant) totalMigrants++; | ||
//TODO DEBUG | ||
if (isMigrant){ | ||
district_migrant_amounts.putIfAbsent(filter.getCategoryKeyOfPerson(pop.getPersons().get(e.getKey())), 0); | ||
district_migrant_amounts.put(filter.getCategoryKeyOfPerson(pop.getPersons().get(e.getKey())), district_migrant_amounts.get(filter.getCategoryKeyOfPerson(pop.getPersons().get(e.getKey())))+1); | ||
} | ||
//DEBUG END< | ||
} | ||
this.migrants = totalMigrants; | ||
} | ||
|
||
public Map<Id<Person>, Double> getMigrantProbabilityMap(){ | ||
return migrantProbabilityMap; | ||
} | ||
|
||
public Population getPopulation(){ | ||
return pop; | ||
} | ||
|
||
public int getMigrantAmount(){ | ||
return migrants; | ||
} | ||
|
||
public static void main(String[] args) { | ||
MigrantMapper detecter = new MigrantMapper( | ||
"../shared-svn/projects/GlaMoBi/matsim-input-files/gladbeck-v1.3-10pct.plans-cleaned.xml.gz", | ||
"../stuff/gladbeck_stadtbezirke_osm_25832.shp", | ||
"Name", | ||
"EPSG:25832" | ||
); | ||
System.out.println(detecter.getMigrantAmount()); | ||
System.out.println(detecter.district_amounts); | ||
System.out.println(detecter.district_migrant_amounts); | ||
|
||
//TODO MATSIM Random | ||
|
||
//DEBUG | ||
|
||
/*Ergebnisse 1 | ||
Mitte I: 182 ~> 1820 vgl. 2670 ( | ||
Mitte II: 143 ~> 1430 vgl. 1122 | ||
Zweckel: 155 ~> 1550 vgl. 1471 | ||
Alt-Rentford: 66 ~> 660 vgl. 230 | ||
Rentford-Nord: 117 ~> 1170 vgl. 1175 | ||
Schultendorf: 28 ~> 280 vgl. 257 | ||
Ellinghorst: 41 ~> 410 vgl. 311 | ||
Ergebnisse 2 | ||
Mitte I: 203/1033=19,65% vgl. 22,20% diff. (-2,55%) | ||
Mitte II: 100/658=15,20% vgl. 14,60% diff. (+0,60%) | ||
Zweckel: 149/944=15,78% vgl. 13,27% diff. (+2,51%) | ||
Alt-Rentford: 71/395=17,97% vgl. 5,30% diff. (+12,67%) | ||
Schultendorf: 38/214=17,76% vgl. 11,02% diff. (+6,74) | ||
Ellinghorst: 30/273=10,99% vgl. 10,48% diff. (+0,51%) | ||
Butendorf: 208/894=23,38% vgl. 21,80% diff. (+1,58%) | ||
Brauck: 257/1014=25,35% vgl. 29,21% diff. (-3,86%) | ||
Rosenhügel: 94/462=20,35% vgl. 20,61% diff. (-0,26%) | ||
TOTAL: 6521 ~> 65210 vgl. 78565 diff. (-17,00%) | ||
TOTAL_M: 1266 ~> 12660 vgl. 14806 diff. (-14,50%) | ||
TOTAL_%: 18,8% vgl. 18,85% | ||
*/ | ||
|
||
} | ||
} |