From 194d63d064e776e22e32e451fe2a7a1c86bcfc7b Mon Sep 17 00:00:00 2001
From: Peter Alexander <peter@blackhillock.co.uk>
Date: Wed, 30 Aug 2017 15:00:23 +0100
Subject: [PATCH] Changes to aggregate FPUs into basins

---
 data/halfdeg/fpuGrouping.txt                  |  12 ++
 hind1970/data/halfdeg/fpuGrouping.txt         |  12 ++
 hind1970/hind_config.properties               |   2 +-
 src/ac/ed/lurg/ModelConfig.java               |   3 +-
 src/ac/ed/lurg/ModelMain.java                 | 110 ++-------------
 src/ac/ed/lurg/landuse/FPUManager.java        | 127 ++++++++++++++----
 src/ac/ed/lurg/landuse/Fpu.java               |  45 +++++++
 .../ed/lurg/landuse/IrrigationRasterSet.java  |  15 +--
 src/ac/ed/lurg/landuse/WaterBasin.java        |  44 ++++++
 src/ac/sac/raster/AbstractRasterReader.java   |   6 +-
 10 files changed, 234 insertions(+), 142 deletions(-)
 create mode 100644 data/halfdeg/fpuGrouping.txt
 create mode 100644 hind1970/data/halfdeg/fpuGrouping.txt
 create mode 100644 src/ac/ed/lurg/landuse/Fpu.java
 create mode 100644 src/ac/ed/lurg/landuse/WaterBasin.java

diff --git a/data/halfdeg/fpuGrouping.txt b/data/halfdeg/fpuGrouping.txt
new file mode 100644
index 00000000..201cb948
--- /dev/null
+++ b/data/halfdeg/fpuGrouping.txt
@@ -0,0 +1,12 @@
+fpu,basin
+70,Nile,
+72,Nile
+74,Nile
+146,Nile
+149,Nile
+150,Nile
+203,Nile
+9,Amazon
+10,Amazon
+12,Amazon
+13,Amazon
diff --git a/hind1970/data/halfdeg/fpuGrouping.txt b/hind1970/data/halfdeg/fpuGrouping.txt
new file mode 100644
index 00000000..201cb948
--- /dev/null
+++ b/hind1970/data/halfdeg/fpuGrouping.txt
@@ -0,0 +1,12 @@
+fpu,basin
+70,Nile,
+72,Nile
+74,Nile
+146,Nile
+149,Nile
+150,Nile
+203,Nile
+9,Amazon
+10,Amazon
+12,Amazon
+13,Amazon
diff --git a/hind1970/hind_config.properties b/hind1970/hind_config.properties
index 5c457f5f..afdb8964 100644
--- a/hind1970/hind_config.properties
+++ b/hind1970/hind_config.properties
@@ -11,7 +11,7 @@ YIELD_DIR=/Users/peteralexander/Documents/LURG/LPJ/Aug2017/LPJG_PLUM_expt1.1_196
 # irritatingly the gams file is also found under the BASE_DIR, and don't want to duplicate so need to specify exactly
 GAMS_MODEL=/Users/peteralexander/Documents/R_Workspace/UNPLUM/GAMS/IntExtOpt.gms
 
-DEBUG_LIMIT_COUNTRIES=false
+DEBUG_LIMIT_COUNTRIES=true
 DEBUG_COUNTRY_NAME=India  & Sri Lanka
 DEBUG_JUST_DEMAND_OUTPUT=false
 IS_CALIBRATION_RUN = false
diff --git a/src/ac/ed/lurg/ModelConfig.java b/src/ac/ed/lurg/ModelConfig.java
index 2e7a69cd..bf6b97ac 100644
--- a/src/ac/ed/lurg/ModelConfig.java
+++ b/src/ac/ed/lurg/ModelConfig.java
@@ -153,6 +153,7 @@ public class ModelConfig {
 	public static final String IRRIGATION_COST_FILE = SPATIAL_DATA_DIR + File.separator + "irrigation_cost.asc";
 	public static final String IRRIGATION_CONSTRAINT_FILE = SPATIAL_DATA_DIR + File.separator + "blue_water_available_pseudoCRU_rcp8p5_2004_2013_grid_allhdyro_mm.txt";
 	public static final String FPU_BOUNDARIES_FILE = SPATIAL_DATA_DIR + File.separator + "FPU.asc";
+	public static final String FPU_GROUPING_FILE = SPATIAL_DATA_DIR + File.separator + "fpuGrouping.txt";
 	public static final String IRRIG_MAX_WATER_FILENAME = getProperty("IRRIG_MAX_WATER_FILENAME", "gsirrigation.out");
 	public static final String IRRIG_RUNOFF_FILE = getProperty("IRRIG_RUNOFF_FILE", "tot_runoff.out");
 	public static final String PROTECTED_AREAS_FILE = SPATIAL_DATA_DIR + File.separator + "protected_areas.txt";
@@ -205,7 +206,7 @@ public class ModelConfig {
 	public static final double PASTURE_HARVEST_FRACTION = getDoubleProperty("PASTURE_HARVEST_FRACTION", 0.5);
 	public static final double MEAT_EFFICIENCY = getDoubleProperty("MEAT_EFFICIENCY", 1.0);  // 'meat' is includes feed conversion ratio already, this is tech. change or similar
 	public static final double IRRIGIATION_EFFICIENCY = getDoubleProperty("IRRIGIATION_EFFICIENCY", 0.5);
-	public static final double ENVIRONMENTAL_WATER_CONSTRAINT = getDoubleProperty("ENVIRONMENTAL_WATER_CONSTRAINT", 0.4);
+	public static final double ENVIRONMENTAL_WATER_CONSTRAINT = getDoubleProperty("ENVIRONMENTAL_WATER_CONSTRAINT", 0.5);
 	public static final double WATER_AVAILIBILITY_RATE_OF_CHANGE = IS_CALIBRATION_RUN ? 0.0 : getDoubleProperty("WATER_AVAILIBILITY_RATE_OF_CHANGE", 0.0);
 	public static final boolean USE_BLUE_WATER_FILE_IRRIG_CONSTRAINT = getBooleanProperty("USE_BLUE_WATER_FILE_IRRIG_CONSTRAINT", false);;
 	
diff --git a/src/ac/ed/lurg/ModelMain.java b/src/ac/ed/lurg/ModelMain.java
index 7e24e6b0..2079c879 100644
--- a/src/ac/ed/lurg/ModelMain.java
+++ b/src/ac/ed/lurg/ModelMain.java
@@ -121,34 +121,8 @@ public class ModelMain {
 
 		prevWorldPrices.put(CropType.PULSES, GlobalPrice.createInitial(0.4 * ModelConfig.INITIAL_PRICE_SHIFT));
 		prevWorldPrices.put(CropType.STARCHY_ROOTS, GlobalPrice.createInitial(0.1 * ModelConfig.INITIAL_PRICE_SHIFT));
-		prevWorldPrices.put(CropType.MONOGASTRICS,
-				GlobalPrice.createInitial(0.4 * 0.5 * ModelConfig.INITIAL_PRICE_SHIFT)); // quantities
-																							// is
-																							// in
-																							// feed
-																							// equivalent
-																							// term
-																							// (0.4
-																							// is
-																							// weighted
-																							// average
-																							// price
-																							// per
-																							// feed,
-																							// and
-																							// 0.5
-																							// accounts
-																							// for
-																							// mark-up
-																							// for
-																							// additional
-																							// processing)
-		prevWorldPrices.put(CropType.RUMINANTS, GlobalPrice.createInitial(0.1 * 0.6 * ModelConfig.INITIAL_PRICE_SHIFT)); // quantities
-																															// is
-																															// in
-																															// feed
-																															// equivalent
-																															// term
+		prevWorldPrices.put(CropType.MONOGASTRICS, GlobalPrice.createInitial(0.4 * 0.5 * ModelConfig.INITIAL_PRICE_SHIFT)); // quantities is in feed equivalent term (0.4 is weighted average price per feed, and 0.5 accounts for mark-up for additional processing)
+		prevWorldPrices.put(CropType.RUMINANTS, GlobalPrice.createInitial(0.1 * 0.6 * ModelConfig.INITIAL_PRICE_SHIFT)); // quantities is in feed equivalent term
 		prevWorldPrices.put(CropType.ENERGY_CROPS, GlobalPrice.createInitial(0.04 * ModelConfig.INITIAL_PRICE_SHIFT));
 		prevStockLevel = getInitialStockLevels();
 	}
@@ -169,12 +143,7 @@ public class ModelMain {
 	private void doTimestep(Timestep timestep) {
 		LogWriter.println(timestep.toString());
 
-		YieldRaster yieldSurfaces = getYieldSurfaces(timestep); // this will
-																// wait for the
-																// marker file
-																// from LPJ if
-																// configured to
-																// do so
+		YieldRaster yieldSurfaces = getYieldSurfaces(timestep); // this will wait for the marker file from LPJ if configured to do so
 		RasterSet<IrrigationItem> allIrrigData = getUpdateIrrigationData(timestep, yieldSurfaces);
 
 		YieldResponsesItem yresp = yieldSurfaces.getFromCoordinates(-90.5, 45.5);
@@ -250,8 +219,7 @@ public class ModelMain {
 			}
 		}
 
-		// energycrops are a special case where demand in global and exogenously
-		// specified
+		// energycrops are a special case where demand in global and exogenously specified
 		totalImportCommodities.incrementValue(CropType.ENERGY_CROPS, gen2EcDDemand);
 
 		// Look at trade balance and adjust appropriately
@@ -300,17 +268,7 @@ public class ModelMain {
 	private void writeLandCoverFile(Timestep timestep, RasterSet<LandUseItem> landUseRaster) {
 		try {
 			StringBuffer sbHeadings = new StringBuffer(
-					"Year,Cropland,Pasture,ManForest,UnmanForest,Natural,EnergyCrop,FertCrop,IrrigCrop"); // Year,Cropland
-																											// (Mha),Pasture
-																											// (Mha),Managed_Forest
-																											// (Mha),Unmanaged_Forest
-																											// (Mha),Natural
-																											// (Mha),Energycrops
-																											// (Mha),Fert
-																											// crop
-																											// (Mt),,Irrig
-																											// crop
-																											// (km3)");
+					"Year,Cropland,Pasture,ManForest,UnmanForest,Natural,EnergyCrop,FertCrop,IrrigCrop");
 			BufferedWriter outputFile = getFileWriter(timestep, ModelConfig.LAND_COVER_OUTPUT_FILE,
 					sbHeadings.toString());
 
@@ -326,14 +284,8 @@ public class ModelMain {
 					LandUseItem.getTotalCropArea(landUseRaster.values(), CropType.ENERGY_CROPS)));
 			sbData.append(String.format(",%.1f",
 					LandUseItem.getFertiliserTotal(landUseRaster.values(), CropType.getCropsLessPasture()) / 1000));
-			// sbData.append(String.format(",%.1f",
-			// LandUseItem.getFertiliserTotal(landUseRaster.values(),
-			// CropType.PASTURE)/1000));
 			sbData.append(String.format(",%.1f",
 					LandUseItem.getIrrigationTotal(landUseRaster.values(), CropType.getCropsLessPasture())));
-			// sbData.append(String.format(",%.1f",
-			// LandUseItem.getIrrigationTotal(landUseRaster.values(),
-			// CropType.PASTURE)));
 
 			outputFile.write(sbData.toString());
 			outputFile.newLine();
@@ -438,21 +390,20 @@ public class ModelMain {
 
 			if (timestep.isInitialTimestep()) {
 				outputClusters(clusterIdRaster);
-				outputWaterAvailablity(currentIrrigationData);
 			}
 		}
+		
+		if (timestep.isInitialTimestep())
+			outputWaterAvailablity(currentIrrigationData);
 
 		// Output LandUses to tabular file, for analysis (perhaps)
 		LogWriter.println("Outputing land uses Year: " + timestep.getYear());
 		LandUseOutputer landuseOutputer = new LandUseOutputer(timestep.getYear(), landUseRaster);
 		landuseOutputer.writeOutput();
 
-		// don't really need this a LPJ outputs have same data, although in a
-		// slightly different format
-		// outputLandCover(timestep.getYear(), landUseRaster,
-		// LandCoverType.CROPLAND);
-		// outputLandCover(timestep.getYear(), landUseRaster,
-		// LandCoverType.PASTURE);
+		// don't really need this a LPJ outputs have same data, although in a slightly different format
+		// outputLandCover(timestep.getYear(), landUseRaster, LandCoverType.CROPLAND);
+		// outputLandCover(timestep.getYear(), landUseRaster, LandCoverType.PASTURE);
 	}
 
 	private void outputWaterAvailablity(IrrigationRasterSet irrigiationRS) {
@@ -510,13 +461,7 @@ public class ModelMain {
 		Collection<CountryAgent> countryAgents = new HashSet<CountryAgent>();
 		Map<CompositeCountry, Map<CropType, CropUsageData>> cropUsageDataMap = new CropUsageReader(
 				compositeCountryManager).getCommodityData();
-		RasterSet<LandUseItem> initLU = getInitialLandUse(); // read in all land
-																// use data
-																// saved from
-																// previous
-																// state, e.g.
-																// calibrations
-																// run
+		RasterSet<LandUseItem> initLU = getInitialLandUse();
 
 		for (CompositeCountry cc : countryGrouping) {
 
@@ -593,22 +538,9 @@ public class ModelMain {
 			c.printStackTrace();
 			return null;
 		}
-
-		// alternative to deserialization is to use tabular data
-		/*
-		 * RasterSet<LandUseItem> initLU = new
-		 * RasterSet<LandUseItem>(desiredProjection) { private static final long
-		 * serialVersionUID = 1317102740312961042L; protected LandUseItem
-		 * createRasterData() { return new LandUseItem(); } }; new
-		 * LandUseReader(initLU).getRasterDataFromFile(ModelConfig.
-		 * INITAL_LAND_USE_FILE); return initLU;
-		 */
 	}
 
-	/**
-	 * this is if we are starting from Hurtt of other initial land covers (so we
-	 * don't have land uses and intensity data)
-	 */
+	/** this is if we are starting from Hurtt of other initial land covers (so we don't have land uses and intensity data) */
 	private RasterSet<LandUseItem> getLandUseFromBaseline() {
 		RasterSet<LandCoverItem> initialLC = new RasterSet<LandCoverItem>(desiredProjection) {
 			private static final long serialVersionUID = 4642550777741425501L;
@@ -622,14 +554,6 @@ public class ModelMain {
 		new MaxCropAreaReader(initialLC).getRasterDataFromFile(ModelConfig.HIGH_SLOPE_AREAS_FILE);
 		new LandCoverReader(initialLC).getRasterDataFromFile(ModelConfig.INITAL_LAND_COVER_FILE);
 
-		/*
-		 * new RasterOutputer<LandCoverItem>(initLC, "InitialCropland") {
-		 * 
-		 * @Override public Double getValue(RasterKey location) { LandCoverItem
-		 * item = results.get(location); if (item == null) return null; return
-		 * item.getLandCoverFract(LandCoverType.CROPLAND); } }.writeOutput();
-		 */
-
 		RasterSet<LandUseItem> landUseRaster = new RasterSet<LandUseItem>(initialLC.getHeaderDetails());
 
 		for (Map.Entry<RasterKey, LandCoverItem> entry : initialLC.entrySet()) {
@@ -637,13 +561,7 @@ public class ModelMain {
 			RasterKey key = entry.getKey();
 			LandCoverItem landCover = entry.getValue();
 			areasItem.setLandCoverAreas(landCover);
-			areasItem.setCropFraction(CropType.WHEAT, 0.5); // random start,
-															// better if we
-															// could get data,
-															// but free
-															// substitution
-															// between crops so
-															// not critical
+			areasItem.setCropFraction(CropType.WHEAT, 0.5); // random start as don't have better data
 			areasItem.setCropFraction(CropType.MAIZE, 0.5);
 			areasItem.setProtectedArea(landCover.getProtectedArea());
 			landUseRaster.put(key, areasItem);
diff --git a/src/ac/ed/lurg/landuse/FPUManager.java b/src/ac/ed/lurg/landuse/FPUManager.java
index 3fa2d967..6ca11ee3 100644
--- a/src/ac/ed/lurg/landuse/FPUManager.java
+++ b/src/ac/ed/lurg/landuse/FPUManager.java
@@ -4,14 +4,14 @@ import java.io.BufferedReader;
 import java.io.FileReader;
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
 import ac.ed.lurg.ModelConfig;
-import ac.ed.lurg.utils.CollectionHelper;
-import ac.ed.lurg.utils.LazyHashMap;
 import ac.ed.lurg.utils.LogWriter;
+import ac.ed.lurg.utils.StringTabularReader;
 import ac.sac.raster.IntegerRasterItem;
 import ac.sac.raster.RasterHeaderDetails;
 import ac.sac.raster.RasterKey;
@@ -22,40 +22,100 @@ public class FPUManager {
 	private static final int YEAR_COL = 1;
 	private static final int USAGE_COL = 2;
 
-	RasterSet<IntegerRasterItem> fpuBoundaries;  // better if these were private
-	Map<IntegerRasterItem, List<RasterKey>> fpuMap;
-	private Map<Integer, Map<Integer, Double>> fpuOtherUses;
+	private StringTabularReader fpuGroups;
+	private Map<WaterBasin, List<RasterKey>> basinToRasterKeysMap = new HashMap<WaterBasin, List<RasterKey>>();
+	private Map<WaterBasin, Map<Integer, Double>> basinToOtherUses = new HashMap<WaterBasin, Map<Integer, Double>>();
+
+	public FPUManager(RasterHeaderDetails desiredProjection) {		
+		fpuGroups = new StringTabularReader(",", new String[]{"fpu", "basin"});
+		fpuGroups.read(ModelConfig.FPU_GROUPING_FILE);
+
+		// Handle rasterkeys
+		Map<Fpu, List<RasterKey>> fpuToRasterKeys = getMapFpuToKeys(desiredProjection);		
+		for (Map.Entry<Fpu, List<RasterKey>> entry : fpuToRasterKeys.entrySet()) {
+			WaterBasin basin = getBasin(entry.getKey());
+			
+			List<RasterKey> basinRasterKeys = basinToRasterKeysMap.get(basin);
+			if (basinRasterKeys == null) {
+				basinRasterKeys = new ArrayList<RasterKey>();
+				basinToRasterKeysMap.put(basin, basinRasterKeys);
+			}
+			basinRasterKeys.addAll(entry.getValue());
+		}
+		
+		// Handle other water uses
+		Map<Fpu, Map<Integer, Double>> fpuToOtherUses = getFpuToOtherUses();
+		for (Map.Entry<Fpu, Map<Integer, Double>> entry : fpuToOtherUses.entrySet()) {
+			WaterBasin basin = getBasin(entry.getKey());
+			
+			Map<Integer, Double> basinOtherUses = basinToOtherUses.get(basin);
+			if (basinOtherUses == null) {
+				basinOtherUses = new HashMap<Integer, Double>();
+				basinToOtherUses.put(basin, basinOtherUses);
+			}
+			
+			for (Map.Entry<Integer, Double> fpuOuEntry : entry.getValue().entrySet()) {
+				Integer year = fpuOuEntry.getKey();
+				Double newOtherUse = fpuOuEntry.getValue();
+				if (basinOtherUses.containsKey(year)) 
+					newOtherUse += basinOtherUses.get(year);
+
+				basinOtherUses.put(year, newOtherUse);
+			}			
+		}
+	}
+	
+	private WaterBasin getBasin(Fpu fpu) {
+		Map<String, String> queryMap = new HashMap<String, String>();
+		queryMap.put("fpu", Integer.toString(fpu.getId()));
+		String basin;
+		try {
+			Map<String, String> row = fpuGroups.querySingle(queryMap);
+			basin = row.get("basin");
+		} catch (Exception e) {
+			basin = fpu.toString(); // this happens whenever an FPU is not grouped explicitly into a basin
+		}
+		return new WaterBasin(basin);
+	}
 
-	public FPUManager(RasterHeaderDetails desiredProjection) {
-		fpuBoundaries = new RasterSet<IntegerRasterItem>(desiredProjection) {
+	private Map<Fpu, List<RasterKey>> getMapFpuToKeys(RasterHeaderDetails desiredProjection) {
+		RasterSet<IntegerRasterItem> fpuBoundaries = new RasterSet<IntegerRasterItem>(desiredProjection) {
 			private static final long serialVersionUID = -8620255271155259176L;
 			protected IntegerRasterItem createRasterData() {
 				return new IntegerRasterItem(0);
 			}
 		};
-
+		
+		// read FPU boundary raster
 		FPUBoundaryReader fpuReader = new FPUBoundaryReader(fpuBoundaries);
 		fpuReader.getRasterDataFromFile(ModelConfig.FPU_BOUNDARIES_FILE);
+		
+		Map<Fpu, List<RasterKey>> invertedBoundaries = new HashMap<Fpu, List<RasterKey>>();
+		
+		for (Map.Entry<RasterKey, IntegerRasterItem> entry : fpuBoundaries.entrySet()) {
+			Fpu fpu = new Fpu(entry.getValue().getInt());
+			List<RasterKey> rasterKeys = invertedBoundaries.get(fpu);
+			if (rasterKeys == null) {
+				rasterKeys = new ArrayList<RasterKey>();
+				invertedBoundaries.put(fpu, rasterKeys);
+			}
+			
+			rasterKeys.add(entry.getKey());
+ 		}
 
-		fpuMap = CollectionHelper.invertMap(fpuBoundaries);
-		readOtherUses();
+		return invertedBoundaries;
 	}
 
-	@SuppressWarnings("serial")
-	private void readOtherUses() {
-
-		LazyHashMap<Integer, Map<Integer, Double>> usageMap = new LazyHashMap<Integer, Map<Integer, Double>>() {
-			protected Map<Integer, Double> createValue() {
-				return new HashMap<Integer, Double>();
-			}
-		};
+	private Map<Fpu, Map<Integer, Double>> getFpuToOtherUses() {
+		Map<Fpu, Map<Integer, Double>> otherUsesMap = new HashMap<Fpu, Map<Integer, Double>>();
 
 		String filename = ModelConfig.OTHER_WATER_USES_FILE;
 		try {
 			BufferedReader reader = new BufferedReader(new FileReader(filename));
 			String line;
-			int fpu, year;
+			int fpuId, year;
 			double waterUsage;
+			Fpu fpu;
 
 			reader.readLine(); // read header
 
@@ -65,11 +125,17 @@ public class FPUManager {
 				if (tokens.length < 3)
 					LogWriter.printlnError("Too few columns in " + filename + ", " + line);
 
-				fpu = Integer.parseInt(tokens[FPU_COL]);
+				fpuId = Integer.parseInt(tokens[FPU_COL]);
+				fpu = new Fpu(fpuId);
 				year = Integer.parseInt(tokens[YEAR_COL]);
 				waterUsage = Double.parseDouble(tokens[USAGE_COL]);
 
-				Map<Integer, Double> fpuData = usageMap.lazyGet(fpu);
+				Map<Integer, Double> fpuData = otherUsesMap.get(fpu);
+				if (fpuData == null) {
+					fpuData = new HashMap<Integer, Double>();
+					otherUsesMap.put(fpu, fpuData);
+				}
+				
 				fpuData.put(year, waterUsage);
 			}
 			reader.close();
@@ -78,21 +144,24 @@ public class FPUManager {
 			LogWriter.printlnError("Failed in reading water usage");
 			LogWriter.print(e);
 		}
-		LogWriter.println("Processed " + filename + ", create " + usageMap.size() + " water usage maps values");
-
-		fpuOtherUses = usageMap;
+		LogWriter.println("Processed " + filename + ", create " + otherUsesMap.size() + " water usage maps values");
+		return otherUsesMap;
 	}
 
-	List<RasterKey> getKeysFor(IntegerRasterItem fpu) {
-		List<RasterKey> keys = fpuMap.get(fpu);
+	public List<RasterKey> getKeysFor(WaterBasin basin) {
+		List<RasterKey> keys = basinToRasterKeysMap.get(basin);
 		if (keys == null)
 			keys = new ArrayList<RasterKey>();
 		return keys;
 	}
 
-	public double getOtherWaterUse(Integer fpuId, Integer year) {
+	public double getOtherWaterUse(WaterBasin basin, Integer year) {
 		//LogWriter.println(fpuId +  " " + year);
-		Double d = fpuOtherUses.get(fpuId).get(year);
+		Double d = basinToOtherUses.get(basin).get(year);
 		return d;
 	}
-}
+
+	public Collection<WaterBasin> getWaterBasins() {
+		return basinToRasterKeysMap.keySet();
+	}
+}
\ No newline at end of file
diff --git a/src/ac/ed/lurg/landuse/Fpu.java b/src/ac/ed/lurg/landuse/Fpu.java
new file mode 100644
index 00000000..5164b04a
--- /dev/null
+++ b/src/ac/ed/lurg/landuse/Fpu.java
@@ -0,0 +1,45 @@
+package ac.ed.lurg.landuse;
+
+/** This is currently really just a marker class to aid type safety, using Integer directly would have worked, but been less clear*/
+
+public class Fpu {
+	private Integer id;
+	
+	Fpu(Integer id) {
+		this.id = id;
+	}
+	
+	public Integer getId() {
+		return id;
+	}
+
+	@Override
+	public String toString() {
+		return "Fpu" + id;
+	}
+
+	@Override
+	public int hashCode() {
+		final int prime = 31;
+		int result = 1;
+		result = prime * result + ((id == null) ? 0 : id.hashCode());
+		return result;
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (this == obj)
+			return true;
+		if (obj == null)
+			return false;
+		if (getClass() != obj.getClass())
+			return false;
+		Fpu other = (Fpu) obj;
+		if (id == null) {
+			if (other.id != null)
+				return false;
+		} else if (!id.equals(other.id))
+			return false;
+		return true;
+	}
+}
diff --git a/src/ac/ed/lurg/landuse/IrrigationRasterSet.java b/src/ac/ed/lurg/landuse/IrrigationRasterSet.java
index 62bfd403..e9f06067 100644
--- a/src/ac/ed/lurg/landuse/IrrigationRasterSet.java
+++ b/src/ac/ed/lurg/landuse/IrrigationRasterSet.java
@@ -5,8 +5,6 @@ import java.util.Map;
 
 import ac.ed.lurg.ModelConfig;
 import ac.ed.lurg.Timestep;
-import ac.ed.lurg.utils.LogWriter;
-import ac.sac.raster.IntegerRasterItem;
 import ac.sac.raster.RasterHeaderDetails;
 import ac.sac.raster.RasterKey;
 import ac.sac.raster.RasterSet;
@@ -28,14 +26,14 @@ public class IrrigationRasterSet extends RasterSet<IrrigationItem> {
 	public void updateConstraintByFPU(Timestep timestep) {
 
 		Collection<RasterKey> fpuKeys;
-		Collection<IntegerRasterItem> keyset = fpuManager.fpuMap.keySet();
+		Collection<WaterBasin> keyset = fpuManager.getWaterBasins();
 
 		int yearsOfIrrigChange = (timestep.getTimestep() - ModelConfig.START_TIMESTEP) * ModelConfig.TIMESTEP_SIZE;
 		double waterAvailabilityAdj = 1.0 + yearsOfIrrigChange * ModelConfig.WATER_AVAILIBILITY_RATE_OF_CHANGE;
 		int year = timestep.getYear();
 		if (year < 2001) year = 2001; // we don't have other water use before this date
 
-		for (IntegerRasterItem fpu : keyset) {
+		for (WaterBasin fpu : keyset) {
 
 			fpuKeys = fpuManager.getKeysFor(fpu);
 			RasterSet<IrrigationItem> irrigData = createSubsetForKeys(fpuKeys);
@@ -48,15 +46,13 @@ public class IrrigationRasterSet extends RasterSet<IrrigationItem> {
 			for (Map.Entry<RasterKey, IrrigationItem> entry : irrigData.entrySet()) {
 
 				if (entry.getValue() != null) {
-					fpuRunOff += entry.getValue().getRunOff() * fpuManager.fpuBoundaries.getAreaMha(entry.getKey())* 0.01;
+					fpuRunOff += entry.getValue().getRunOff() * irrigData.getAreaMha(entry.getKey())* 0.01;
 					cellCount++;
 				}
 			}
-
 			
 			Double fpuRunOffAvailable = fpuRunOff * ModelConfig.ENVIRONMENTAL_WATER_CONSTRAINT; 
-			
-			otherUses = fpuManager.getOtherWaterUse(fpu.getInt(), year) * waterAvailabilityAdj;
+			otherUses = fpuManager.getOtherWaterUse(fpu, year) * waterAvailabilityAdj;
 			
 			if (fpuRunOffAvailable - otherUses < 0){
 				waterAvailForIrrigPerCell = 0;
@@ -64,10 +60,9 @@ public class IrrigationRasterSet extends RasterSet<IrrigationItem> {
 			else
 				waterAvailForIrrigPerCell = (fpuRunOffAvailable - otherUses) / cellCount;
 				
-
 			for (Map.Entry<RasterKey, IrrigationItem> entry : irrigData.entrySet()) {
 				if (entry.getValue() != null){
-					waterAvailabileMm = waterAvailForIrrigPerCell/fpuManager.fpuBoundaries.getAreaMha(entry.getKey())/0.01;
+					waterAvailabileMm = waterAvailForIrrigPerCell/irrigData.getAreaMha(entry.getKey())/0.01;
 					entry.getValue().setIrrigConstraint(waterAvailabileMm);
 				}
 			}
diff --git a/src/ac/ed/lurg/landuse/WaterBasin.java b/src/ac/ed/lurg/landuse/WaterBasin.java
new file mode 100644
index 00000000..2d4a5c73
--- /dev/null
+++ b/src/ac/ed/lurg/landuse/WaterBasin.java
@@ -0,0 +1,44 @@
+package ac.ed.lurg.landuse;
+
+public class WaterBasin {
+
+	private String basinId;
+
+	public WaterBasin (String basinId) {
+		this.basinId = basinId;
+	}
+
+	public String getId() {
+		return basinId;
+	}
+
+	@Override
+	public String toString() {
+		return "WaterBasin " + basinId;
+	}
+
+	@Override
+	public int hashCode() {
+		final int prime = 31;
+		int result = 1;
+		result = prime * result + ((basinId == null) ? 0 : basinId.hashCode());
+		return result;
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (this == obj)
+			return true;
+		if (obj == null)
+			return false;
+		if (getClass() != obj.getClass())
+			return false;
+		WaterBasin other = (WaterBasin) obj;
+		if (basinId == null) {
+			if (other.basinId != null)
+				return false;
+		} else if (!basinId.equals(other.basinId))
+			return false;
+		return true;
+	}
+}
diff --git a/src/ac/sac/raster/AbstractRasterReader.java b/src/ac/sac/raster/AbstractRasterReader.java
index a1560b22..d4507a1b 100755
--- a/src/ac/sac/raster/AbstractRasterReader.java
+++ b/src/ac/sac/raster/AbstractRasterReader.java
@@ -101,13 +101,9 @@ public abstract class AbstractRasterReader<D extends RasterItem> {
 		int row = 0, col = 0;
 		
 		try {
-			
 			BufferedReader in = new BufferedReader(new FileReader(filename));
 			RasterHeaderDetails header = handleHeader(in);
-					
-			String line;
-			
-			
+			String line;			
 			
 			while ((line=in.readLine()) != null) {
 				
-- 
GitLab