From f59f81a83afe4050a8794fdea5c6e946e3c6a7f0 Mon Sep 17 00:00:00 2001 From: Matthew Wigginton Conway Date: Mon, 5 Jan 2015 16:11:23 -0500 Subject: [PATCH 01/29] write complete pointsets to cluster cache. --- app/controllers/Api.java | 7 +-- app/models/Query.java | 8 +-- app/models/Shapefile.java | 82 ++++++++++++++---------------- app/otp/AnalystProfileRequest.java | 10 +++- app/otp/AnalystRequest.java | 4 +- app/tiles/AnalystTileRequest.java | 11 +++- 6 files changed, 66 insertions(+), 56 deletions(-) diff --git a/app/controllers/Api.java b/app/controllers/Api.java index 0d1924f..80c59c4 100644 --- a/app/controllers/Api.java +++ b/app/controllers/Api.java @@ -297,7 +297,7 @@ public static Result result(Integer surfaceId, String shapefileId, String attrib } ByteArrayOutputStream baos = new ByteArrayOutputStream(); - result.writeJson(baos, shp.getPointSet(attributeName)); + result.writeJson(baos, shp.getPointSet()); ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); response().setContentType("application/json"); return ok(bais); @@ -625,9 +625,10 @@ public static Result createShapefile() throws ZipException, IOException { if (file != null && file.getFile() != null) { - Shapefile s = Shapefile.create(file.getFile(), body.asFormUrlEncoded().get("projectId")[0]); + String projectId = body.asFormUrlEncoded().get("projectId")[0]; + String name = body.asFormUrlEncoded().get("name")[0]; + Shapefile s = Shapefile.create(file.getFile(), projectId, name); - s.name = body.asFormUrlEncoded().get("name")[0]; s.description = body.asFormUrlEncoded().get("description")[0]; s.save(); diff --git a/app/models/Query.java b/app/models/Query.java index c196649..a8155e1 100644 --- a/app/models/Query.java +++ b/app/models/Query.java @@ -276,8 +276,6 @@ public void onReceive(Object message) throws Exception { if (workOffline == null) workOffline = true; - String pointSetCachedName = sl.writeToClusterCache(workOffline, q.attributeName); - ActorSystem system = Cluster.getActorSystem(); ActorRef executive = Cluster.getExecutive(); @@ -286,12 +284,14 @@ public void onReceive(Object message) throws Exception { if (q.isTransit()) { // create a profile request ProfileRequest pr = Api.analyst.buildProfileRequest(q.mode, q.date, q.fromTime, q.toTime, null); - js = new JobSpec(q.scenarioId, pointSetCachedName, pointSetCachedName, pr); + // the pointset is already in the cluster cache, from when it was uploaded. + // every pointset has all shapefile attributes. + js = new JobSpec(q.scenarioId, sl.id, sl.id, pr); } else { // this is not a transit request, no need for computationally-expensive profile routing RoutingRequest rr = Api.analyst.buildRequest(q.scenarioId, q.date, q.fromTime, null, q.mode, 120); - js = new JobSpec(q.scenarioId, pointSetCachedName, pointSetCachedName, rr); + js = new JobSpec(q.scenarioId, sl.id, sl.id, rr); } // plus a callback that registers how many work items have returned diff --git a/app/models/Shapefile.java b/app/models/Shapefile.java index 57a727a..696d645 100644 --- a/app/models/Shapefile.java +++ b/app/models/Shapefile.java @@ -95,7 +95,8 @@ public class Shapefile implements Serializable { @JsonIgnore public HashMap attributes = new HashMap(); - public static Map pointSetCache = new ConcurrentHashMap(); + /** the pointset for this shapefile */ + private transient PointSet pointSet; @JsonIgnore public File file; @@ -109,7 +110,7 @@ public class Shapefile implements Serializable { public Shapefile() { } - static public class ShapeFeature implements Serializable, Comparable { + static public class ShapeFeature implements Serializable, Comparable { private static final long serialVersionUID = 1L; public String id; @@ -238,63 +239,50 @@ public synchronized STRtree getSpatialIndex() { } @JsonIgnore - public PointSet getPointSet(String attrName) { + public synchronized PointSet getPointSet() { + if (pointSet != null) + return pointSet; - String psId = this.id + "_" + attrName; - - synchronized (pointSetCache) { - if(pointSetCache.containsKey(psId)) - return pointSetCache.get(psId); - - PointSet ps = new PointSet(getFeatureCount()); + pointSet = new PointSet(getFeatureCount()); - String categoryId = Attribute.convertNameToId(this.name); + String categoryId = Attribute.convertNameToId(this.name); - ps.id = categoryId; - ps.label = this.name; - ps.description = this.description; + pointSet.id = categoryId; + pointSet.label = this.name; + pointSet.description = this.description; - int index = 0; - for(ShapeFeature sf : this.getShapeFeatureStore().getAll()) { + int index = 0; + for(ShapeFeature sf : this.getShapeFeatureStore().getAll()) { - HashMap propertyData = new HashMap(); + HashMap propertyData = new HashMap(); - Attribute a = this.attributes.get(attrName); + for (Attribute a : this.attributes.values()) { String propertyId = categoryId + "." + Attribute.convertNameToId(a.name); propertyData.put(propertyId, sf.getAttribute(a.fieldName)); + } - PointFeature pf; - try { - pf = new PointFeature(sf.id.toString(), sf.geom, propertyData); - ps.addFeature(pf, index); - } catch (EmptyPolygonException | UnsupportedGeometryException e) { - e.printStackTrace(); - } - - - index++; + PointFeature pf; + try { + pf = new PointFeature(sf.id.toString(), sf.geom, propertyData); + pointSet.addFeature(pf, index); + } catch (EmptyPolygonException | UnsupportedGeometryException e) { + e.printStackTrace(); } - ps.setLabel(categoryId, this.name); - Attribute attr = this.attributes.get(attrName); - String propertyId = categoryId + "." + Attribute.convertNameToId(attr.name); - ps.setLabel(propertyId, attr.name); - - if (attr.color != null) - ps.setStyle(propertyId, "color", attr.color); + index++; + } - pointSetCache.put(psId, ps); + pointSet.setLabel(categoryId, this.name); - return ps; - } + return pointSet; } - public String writeToClusterCache(Boolean workOffline, String attrName) throws IOException { + private String writeToClusterCache() throws IOException { - PointSet ps = this.getPointSet(attrName); - String cachePointSetId = id + "_" + Attribute.convertNameToId(attrName) + ".json"; + PointSet ps = this.getPointSet(); + String cachePointSetId = id + ".json"; File f = new File(cachePointSetId); @@ -304,6 +292,7 @@ public String writeToClusterCache(Boolean workOffline, String attrName) throws I String s3credentials = Play.application().configuration().getString("cluster.s3credentials"); String bucket = Play.application().configuration().getString("cluster.pointsets-bucket"); + boolean workOffline = Play.application().configuration().getBoolean("cluster.work-offline"); PointSetDatastore datastore = new PointSetDatastore(10, s3credentials, workOffline, bucket); @@ -390,7 +379,10 @@ public Collection queryAll() { } - public static Shapefile create(File originalShapefileZip, String projectId) throws ZipException, IOException { + /** + * Create a new shapefile with the given name. + */ + public static Shapefile create(File originalShapefileZip, String projectId, String name) throws ZipException, IOException { String shapefileHash = HashUtils.hashFile(originalShapefileZip); @@ -410,6 +402,8 @@ public static Shapefile create(File originalShapefileZip, String projectId) thro shapefile.id = shapefileId; shapefile.projectId = projectId; + + shapefile.name = name; ZipFile zipFile = new ZipFile(originalShapefileZip); @@ -445,7 +439,9 @@ public static Shapefile create(File originalShapefileZip, String projectId) thro shapefile.setShapeFeatureStore(features); shapefile.save(); - + + shapefile.writeToClusterCache(); + Logger.info("done loading shapefile " + shapefileId); } else diff --git a/app/otp/AnalystProfileRequest.java b/app/otp/AnalystProfileRequest.java index d3f9926..8b5a0df 100644 --- a/app/otp/AnalystProfileRequest.java +++ b/app/otp/AnalystProfileRequest.java @@ -9,8 +9,10 @@ import models.Shapefile; import models.SpatialLayer; +import org.opentripplanner.analyst.PointSet; import org.opentripplanner.analyst.ResultSet; import org.opentripplanner.analyst.ResultSetWithTimes; +import org.opentripplanner.analyst.SampleSet; import org.opentripplanner.analyst.SurfaceCache; import org.opentripplanner.analyst.TimeSurface; import org.opentripplanner.api.model.TimeSurfaceShort; @@ -80,7 +82,9 @@ public static ResultSet getResult(Integer surfaceId, String shapefileId, String else { TimeSurface surf =getSurface(surfaceId); - result = new ResultSet(Shapefile.getShapefile(shapefileId).getPointSet(attributeName).getSampleSet(surf.routerId), surf); + PointSet ps = Shapefile.getShapefile(shapefileId).getPointSet(); + SampleSet ss = ps.getSampleSet(Api.analyst.getGraph(surf.routerId)); + result = new ResultSet(ss, surf); resultCache.put(resultId, result); } } @@ -104,7 +108,9 @@ public static ResultSetWithTimes getResultWithTimes(Integer surfaceId, String sh else { TimeSurface surf = getSurface(surfaceId); - resultWithTimes = new ResultSetWithTimes(Shapefile.getShapefile(shapefileId).getPointSet(attributeName).getSampleSet(surf.routerId), surf); + PointSet ps = Shapefile.getShapefile(shapefileId).getPointSet(); + SampleSet ss = ps.getSampleSet(Api.analyst.getGraph(surf.routerId)); + resultWithTimes = new ResultSetWithTimes(ss, surf); resultCache.put(resultId, resultWithTimes); } } diff --git a/app/otp/AnalystRequest.java b/app/otp/AnalystRequest.java index f27d782..c4c52d5 100644 --- a/app/otp/AnalystRequest.java +++ b/app/otp/AnalystRequest.java @@ -92,7 +92,7 @@ public static ResultSet getResult(Integer surfaceId, String shapefileId, String result = resultCache.get(resultId); else { TimeSurface surf =getSurface(surfaceId); - PointSet ps = Shapefile.getShapefile(shapefileId).getPointSet(attributeName); + PointSet ps = Shapefile.getShapefile(shapefileId).getPointSet(); SampleSet ss = ps.getSampleSet(Api.analyst.getGraph(surf.routerId)); result = new ResultSet(ss, surf); resultCache.put(resultId, result); @@ -113,7 +113,7 @@ public static ResultSetWithTimes getResultWithTimes(Integer surfaceId, String sh resultWithTimes = (ResultSetWithTimes)resultCache.get(resultId); else { TimeSurface surf =getSurface(surfaceId); - PointSet ps = Shapefile.getShapefile(shapefileId).getPointSet(attributeName); + PointSet ps = Shapefile.getShapefile(shapefileId).getPointSet(); SampleSet ss = ps.getSampleSet(Api.analyst.getGraph(surf.routerId)); resultWithTimes = new ResultSetWithTimes(ss, surf); resultCache.put(resultId, resultWithTimes); diff --git a/app/tiles/AnalystTileRequest.java b/app/tiles/AnalystTileRequest.java index bfc9c27..7c8c607 100644 --- a/app/tiles/AnalystTileRequest.java +++ b/app/tiles/AnalystTileRequest.java @@ -20,6 +20,7 @@ import org.opentripplanner.analyst.PointSet; import org.opentripplanner.analyst.ResultSetDelta; import org.opentripplanner.analyst.ResultSetWithTimes; +import org.opentripplanner.analyst.SampleSet; import org.opentripplanner.analyst.TimeSurface; import otp.AnalystProfileRequest; @@ -40,6 +41,8 @@ import com.google.common.hash.Hashing; import com.vividsolutions.jts.index.strtree.STRtree; +import controllers.Api; + public abstract class AnalystTileRequest { private static TransportIndex transitIndex = new TransportIndex(); @@ -388,8 +391,12 @@ public byte[] render(){ if (surf2 == null) surf2 = AnalystRequest.getSurface(surfaceId2); - PointSet ps = shp.getPointSet(attributeName); - ResultSetDelta resultDelta = new ResultSetDelta(ps.getSampleSet(surf1.routerId), ps.getSampleSet(surf2.routerId), surf1, surf2); + PointSet ps = shp.getPointSet(); + + // TODO: cache samples on multiple tile requests (should be a performance win) + SampleSet ss1 = ps.getSampleSet(Api.analyst.getGraph(surf1.routerId)); + SampleSet ss2 = ps.getSampleSet(Api.analyst.getGraph(surf2.routerId)); + ResultSetDelta resultDelta = new ResultSetDelta(ss1, ss2, surf1, surf2); List features = shp.query(tile.envelope); From 8e88cb38fa40b5d6aba563851d806b15f85f6b3b Mon Sep 17 00:00:00 2001 From: Matthew Wigginton Conway Date: Tue, 6 Jan 2015 09:47:56 -0500 Subject: [PATCH 02/29] Single point UI for using a complete pointset. --- app/models/Attribute.java | 4 + app/models/Shapefile.java | 2 + public/javascripts/analysis-single.js | 176 +++++++++----------------- public/javascripts/models.js | 7 + 4 files changed, 75 insertions(+), 114 deletions(-) diff --git a/app/models/Attribute.java b/app/models/Attribute.java index 6fe59c7..9054f3c 100644 --- a/app/models/Attribute.java +++ b/app/models/Attribute.java @@ -53,6 +53,10 @@ else if(f instanceof Long) count++; } + /** + * Sanitize a name for use as a category ID. Also implemented in the client: + * A.models.Shapefile.getCategoryName() + */ static String convertNameToId(String name) { return name.toLowerCase().trim().replaceAll(" ", "_").replaceAll("\\W",""); } diff --git a/app/models/Shapefile.java b/app/models/Shapefile.java index 696d645..839487e 100644 --- a/app/models/Shapefile.java +++ b/app/models/Shapefile.java @@ -259,6 +259,8 @@ public synchronized PointSet getPointSet() { for (Attribute a : this.attributes.values()) { String propertyId = categoryId + "." + Attribute.convertNameToId(a.name); propertyData.put(propertyId, sf.getAttribute(a.fieldName)); + // TODO: update names when attribute name is edited. + pointSet.setLabel(propertyId, a.name); } diff --git a/public/javascripts/analysis-single.js b/public/javascripts/analysis-single.js index b43fcad..5ba3923 100644 --- a/public/javascripts/analysis-single.js +++ b/public/javascripts/analysis-single.js @@ -380,37 +380,39 @@ var Analyst = Analyst || {}; this.$('#queryProcessing').hide(); this.$('#queryResults').show(); + var categoryId = this.shapefiles.get(this.$("#shapefile").val()).getCategoryId(); + var attributeId = this.$('#shapefileColumn').val() + if(this.comparisonType == 'compare') { if(this.surfaceId1) { - var resUrl = '/api/result?shapefileId=' + this.$("#shapefile").val() + '&attributeName=' + this.$('#shapefileColumn').val() + + var resUrl = '/api/result?shapefileId=' + this.$("#shapefile").val() + '&attributeName=' + attributeId + '&surfaceId=' + this.surfaceId1 + '&which=' + this.$('input[name="which"]:checked').val(); $.getJSON(resUrl, function(res) { - _this.scenario1Data = res; - _this.drawChart(res, 1, "#barChart1", 175); + _this.drawChart(res, categoryId + '.' + attributeId, 1, "#barChart1", 175); }); } if(this.surfaceId2) { - var resUrl = '/api/result?shapefileId=' + this.$("#shapefile").val() + '&attributeName=' + this.$('#shapefileColumn').val() + + var resUrl = '/api/result?shapefileId=' + this.$("#shapefile").val() + '&attributeName=' + attributeId + '&surfaceId=' + this.surfaceId2 + '&which=' + this.$('input[name="which"]:checked').val(); $.getJSON(resUrl, function(res) { _this.scenario2Data = res; - _this.drawChart(res, 2, "#barChart2", 175); + _this.drawChart(res, categoryId + '.' + attributeId, 2, "#barChart2", 175); }); } } else { - var resUrl = '/api/result?shapefileId=' + this.$("#shapefile").val() + '&attributeName=' + this.$('#shapefileColumn').val() + + var resUrl = '/api/result?shapefileId=' + this.$("#shapefile").val() + '&attributeName=' + attributeId + '&surfaceId=' + this.surfaceId1 + '&which=' + this.$('input[name="which"]:checked').val(); $.getJSON(resUrl, function(res) { - _this.drawChart(res, 1, "#barChart1", 175); + _this.drawChart(res, categoryId + '.' + attributeId, 1, "#barChart1", 175); }); } @@ -517,125 +519,71 @@ var Analyst = Analyst || {}; this.$('#querySettings').show(); }, - drawChart : function(res, barChart, divSelector, chartHeight) { - + /** + * Draw a chart using the given attribute contained within the given result, in the given div. + * chartIdx is an integer [1,2] specifying whether to render the first or second chart. + */ + drawChart : function(result, attribute, chartIdx, divSelector, chartHeight) { var _this = this; - var color = new Array(); - var label = new Array(); - var value = new Array(); - var id = new Array(); - + var color = result.properties.schema[attribute].style.color; - $() - - _.each(res.data, function(val,key) { - - id.push(key); - value.push(val.sums); - label.push(res.properties.schema[key].label); - color.push(res.properties.schema[key].style.color); - - }); + var barChart = dc.barChart(divSelector); - var transformedData = new Array(); - - var minute = 1; - var item = {}; - - var maxVals = {}; - - for(i in id) { - item[id[i]] = 0; + // pivot the data to make it ready for crossfilter + var data = []; + for (var i = 0; i < 120; i++) { + data.push({minute: i, value: result.data[attribute].sums[i]}); } - for(var v in value[0]) { - - maxVals[v] = 0; - - if(minute % 1 == 0) { - item['min'] = Math.ceil(minute / 1); - - for(i in id) { - item[id[i]] = item[id[i]] + parseInt(value[i][v]); - maxVals[v] = maxVals[v] + parseInt(value[i][v]); - } - - transformedData.push(item); + var cfData = crossfilter(data); - item = {}; - - for(i in id) { - item[id[i]] = 0; - } - } - else { - for(i in id) { - item[id[i]] = item[id[i]] + parseInt(value[i][v]); - } - } - - minute++; - } - - for(var v in maxVals) { - if(maxVals[v] > this.maxChartValue) - this.maxChartValue = maxVals[v]; - } + var minuteDimension = cfData.dimension(function (d) { + return d.minute; + }); - var minuteDimension; + // aggregate to bins + var aggregated = minuteDimension.group(function (minute) { + return Math.floor(minute / 5) * 5; + }) + .reduceSum(function (d) { + return d.value; + }); - if(barChart == 1) { - this.barChart1 = dc.barChart(divSelector); - this.cfData1 = crossfilter(transformedData); - minuteDimension = this.cfData1.dimension(function(d) { - return d.min; - }); - barChart = this.barChart1; - } - else if(barChart == 2) { - this.barChart2 = dc.barChart(divSelector); - this.cfData2 = crossfilter(transformedData); - minuteDimension = this.cfData2.dimension(function(d) { - return d.min; - }); - barChart = this.barChart2; - } + var maxVal = aggregated.top(1)[0].value; + if (maxVal > this.maxChartValue) + this.maxChartValue = maxVal; barChart - .width(400) - .height(chartHeight) - .margins({top: 10, right: 20, bottom: 10, left: 40}) - .elasticY(false) - .y(d3.scale.linear().domain([0, this.maxChartValue])) - .dimension(minuteDimension) - .ordinalColors(color) - .xAxisLabel("Minutes") - .yAxisLabel("# " + res.properties.label) - .transitionDuration(0); - - - for(i in id) { - var group = minuteDimension.group().reduceSum(function(d){return d[id[i]]}); - - if(i == 0) - barChart.group(group, label[i]) - else - barChart.stack(group, label[i]) - } - - barChart.x(d3.scale.linear().domain([0, 120])) - .renderHorizontalGridLines(true) - .centerBar(true) - .brushOn(false) - .legend(dc.legend().x(250).y(10)) - .xAxis().ticks(5).tickFormat(d3.format("d")); - - this.scaleBarCharts(); - - dc.renderAll(); - + .width(400) + // maximize data:ink ratio + .gap(0.1) + .height(chartHeight) + .margins({top: 10, right: 20, bottom: 10, left: 40}) + .elasticY(false) + .y(d3.scale.linear().domain([0, this.maxChartValue])) + .dimension(minuteDimension) + .ordinalColors([color]) + .xAxisLabel("Minutes") + .yAxisLabel(result.properties.schema[attribute].label) + .transitionDuration(0) + .group(aggregated, result.properties.schema[attribute].label) + .x(d3.scale.linear().domain([0, 120])) + .renderHorizontalGridLines(true) + .centerBar(true) + .brushOn(false) + // get the number of bins so that the bar width is correct in the histogram. + // see https://github.com/dc-js/dc.js/issues/137 + .xUnits(function () { return aggregated.size(); }) + .xAxis().ticks(5).tickFormat(d3.format("d")); + + this['barChart' + chartIdx] = barChart; + this['cfData' + chartIdx] = cfData; + + this.scaleBarCharts(); + + dc.renderAll(); }, /*updateSummary : function() { diff --git a/public/javascripts/models.js b/public/javascripts/models.js index 67ab56e..c84a547 100644 --- a/public/javascripts/models.js +++ b/public/javascripts/models.js @@ -51,6 +51,13 @@ var Analyst = Analyst || {}; return _.filter(this.get('shapeAttributes'), function (a) { return a.numeric; }) + }, + + /** + * Get the category ID of this shapefile in a pointset. This does the same thing as Attribute.convertNameToId. + */ + getCategoryId : function () { + return this.get('name').toLowerCase().trim().replace(/ /g, '_').replace(/\W+/g, ''); } }); From 5046606233a36c32ddacd94cf5897bce843752e1 Mon Sep 17 00:00:00 2001 From: Matthew Wigginton Conway Date: Tue, 6 Jan 2015 11:02:08 -0500 Subject: [PATCH 03/29] rip out passing of attribute name in all single point stuff. --- app/controllers/Api.java | 6 +-- app/controllers/Gis.java | 27 +++++++---- app/controllers/Tiles.java | 4 +- app/otp/AnalystProfileRequest.java | 8 ++-- app/otp/AnalystRequest.java | 8 ++-- app/tiles/AnalystTileRequest.java | 10 ++-- conf/routes | 6 +-- public/javascripts/analysis-single.js | 69 ++++++++++++++++----------- 8 files changed, 79 insertions(+), 59 deletions(-) diff --git a/app/controllers/Api.java b/app/controllers/Api.java index 80c59c4..2741238 100644 --- a/app/controllers/Api.java +++ b/app/controllers/Api.java @@ -284,16 +284,16 @@ public static Result isochrone(Integer surfaceId, List cutoffs) throws * @param shapefileId * @return */ - public static Result result(Integer surfaceId, String shapefileId, String attributeName) { + public static Result result(Integer surfaceId, String shapefileId) { final Shapefile shp = Shapefile.getShapefile(shapefileId); ResultSet result; // it could be a profile request, or not // The IDs are unique; they come from inside OTP. try { - result = AnalystProfileRequest.getResult(surfaceId, shapefileId, attributeName); + result = AnalystProfileRequest.getResult(surfaceId, shapefileId); } catch (NullPointerException e) { - result = AnalystRequest.getResult(surfaceId, shapefileId, attributeName); + result = AnalystRequest.getResult(surfaceId, shapefileId); } ByteArrayOutputStream baos = new ByteArrayOutputStream(); diff --git a/app/controllers/Gis.java b/app/controllers/Gis.java index 91f702b..d7a7ecb 100644 --- a/app/controllers/Gis.java +++ b/app/controllers/Gis.java @@ -216,14 +216,14 @@ public static Result query(String queryId, Integer timeLimit, String weightBySha } - public static Result surface(Integer surfaceId, String shapefileId, String attributeName, Integer timeLimit, String compareTo) { + public static Result surface(Integer surfaceId, String shapefileId, Integer timeLimit, String compareTo) { response().setHeader(CACHE_CONTROL, "no-cache, no-store, must-revalidate"); response().setHeader(PRAGMA, "no-cache"); response().setHeader(EXPIRES, "0"); - String shapeName = (timeLimit / 60) + "_mins_" + shapefileId.toString().toLowerCase() + "_" + surfaceId.toString() + "_" + attributeName; + String shapeName = (timeLimit / 60) + "_mins_" + shapefileId.toString().toLowerCase() + "_" + surfaceId.toString(); try { @@ -237,19 +237,23 @@ public static Result surface(Integer surfaceId, String shapefileId, String attri ResultSetWithTimes result; try { - result = AnalystProfileRequest.getResultWithTimes(surfaceId, shapefileId, attributeName); + result = AnalystProfileRequest.getResultWithTimes(surfaceId, shapefileId); } catch (NullPointerException e) { // not a profile request - result = AnalystRequest.getResultWithTimes(surfaceId, shapefileId, attributeName); + result = AnalystRequest.getResultWithTimes(surfaceId, shapefileId); } Collection features = shp.getShapeFeatureStore().getAll(); ArrayList fields = new ArrayList(); - - fields.add(shp.name.replaceAll("\\W+", "")); - + + for (Attribute a : shp.attributes.values()) { + if (a.numeric) { + fields.add(a.name); + } + } + ArrayList gisFeatures = new ArrayList(); for(ShapeFeature feature : features) { @@ -262,7 +266,12 @@ public static Result surface(Integer surfaceId, String shapefileId, String attri gf.id = feature.id; gf.time = result.getTime(feature.id); - gf.fields.add(feature.getAttribute(attributeName)); + // TODO: handle non-integer attributes + for (Attribute a : shp.attributes.values()) { + if (a.numeric) { + gf.fields.add(feature.getAttribute(a.name)); + } + } gisFeatures.add(gf); @@ -333,7 +342,7 @@ static File generateZippedShapefile(String fileName, ArrayList fieldName featureDefinition += "String"; if(features.get(0).fields.get(fieldPosition) instanceof Number) featureDefinition += "Double"; - fieldPosition++; + fieldPosition++; } SimpleFeatureType featureType = DataUtilities.createType("Analyst", featureDefinition); diff --git a/app/controllers/Tiles.java b/app/controllers/Tiles.java index 8d7ba5c..9625a47 100644 --- a/app/controllers/Tiles.java +++ b/app/controllers/Tiles.java @@ -108,10 +108,10 @@ public static Promise shape(String shapefileId, Integer x, Integer y, In return tileBuilder(tileRequest); } - public static Promise surface(Integer surfaceId, String shapefileId, String attributeName, Integer x, Integer y, Integer z, + public static Promise surface(Integer surfaceId, String shapefileId, Integer x, Integer y, Integer z, Boolean showIso, Boolean showPoints, Integer timeLimit, Integer minTime) { - AnalystTileRequest tileRequest = new SurfaceTile( surfaceId, shapefileId, attributeName, x, y, z, showIso, showPoints, timeLimit, minTime); + AnalystTileRequest tileRequest = new SurfaceTile( surfaceId, shapefileId, x, y, z, showIso, showPoints, timeLimit, minTime); return tileBuilder(tileRequest); } diff --git a/app/otp/AnalystProfileRequest.java b/app/otp/AnalystProfileRequest.java index 8b5a0df..b99f7b0 100644 --- a/app/otp/AnalystProfileRequest.java +++ b/app/otp/AnalystProfileRequest.java @@ -70,9 +70,9 @@ public static TimeSurfaceShort createSurfaces(ProfileRequest req, String graphId * Get the ResultSet for the given ID. Note that no ResultEnvelope.Which need be specified as each surface ID is unique to a particular * statistic. */ - public static ResultSet getResult(Integer surfaceId, String shapefileId, String attributeName) { + public static ResultSet getResult(Integer surfaceId, String shapefileId) { - String resultId = "resultId_" + surfaceId + "_" + shapefileId + "_" + attributeName; + String resultId = "resultId_" + surfaceId + "_" + shapefileId; ResultSet result; @@ -96,9 +96,9 @@ public static ResultSet getResult(Integer surfaceId, String shapefileId, String * Get the ResultSet for the given ID. Note that no min/max need be specified as each surface ID is unique to a particular * statistic. */ - public static ResultSetWithTimes getResultWithTimes(Integer surfaceId, String shapefileId, String attributeName) { + public static ResultSetWithTimes getResultWithTimes(Integer surfaceId, String shapefileId) { - String resultId = "resultWithTimesId_" + surfaceId + "_" + shapefileId + "_" + attributeName; + String resultId = "resultWithTimesId_" + surfaceId + "_" + shapefileId; ResultSetWithTimes resultWithTimes; diff --git a/app/otp/AnalystRequest.java b/app/otp/AnalystRequest.java index c4c52d5..aa212bf 100644 --- a/app/otp/AnalystRequest.java +++ b/app/otp/AnalystRequest.java @@ -81,9 +81,9 @@ public static TimeSurfaceShort createSurface(RoutingRequest req, int cutoffMinut } - public static ResultSet getResult(Integer surfaceId, String shapefileId, String attributeName) { + public static ResultSet getResult(Integer surfaceId, String shapefileId) { - String resultId = "resultId_" + surfaceId + "_" + shapefileId + "_" + attributeName; + String resultId = "resultId_" + surfaceId + "_" + shapefileId; ResultSet result; @@ -102,9 +102,9 @@ public static ResultSet getResult(Integer surfaceId, String shapefileId, String return result; } - public static ResultSetWithTimes getResultWithTimes(Integer surfaceId, String shapefileId, String attributeName) { + public static ResultSetWithTimes getResultWithTimes(Integer surfaceId, String shapefileId) { - String resultId = "resultWIthTimesId_" + surfaceId + "_" + shapefileId + "_" + attributeName; + String resultId = "resultWithTimesId_" + surfaceId + "_" + shapefileId; ResultSetWithTimes resultWithTimes; diff --git a/app/tiles/AnalystTileRequest.java b/app/tiles/AnalystTileRequest.java index 7c8c607..1ff62b4 100644 --- a/app/tiles/AnalystTileRequest.java +++ b/app/tiles/AnalystTileRequest.java @@ -480,14 +480,12 @@ public static class SurfaceTile extends AnalystTileRequest { final Boolean showPoints; final Integer timeLimit; final Integer minTime; - final String attributeName; - public SurfaceTile(Integer surfaceId, String shapefileId, String attributeName, Integer x, Integer y, Integer z, + public SurfaceTile(Integer surfaceId, String shapefileId, Integer x, Integer y, Integer z, Boolean showIso, Boolean showPoints, Integer timeLimit, Integer minTime) { super(x, y, z, "surface"); this.shapefileId = shapefileId; - this.attributeName = attributeName; this.surfaceId = surfaceId; this.showIso = showIso; this.showPoints = showPoints; @@ -496,7 +494,7 @@ public SurfaceTile(Integer surfaceId, String shapefileId, String attributeName, } public String getId() { - return super.getId() + "_" + shapefileId + "_" + attributeName + "_" + surfaceId + "_" + showIso + "_" + showPoints + "_" + timeLimit + "_" + minTime; + return super.getId() + "_" + shapefileId + "_" + surfaceId + "_" + showIso + "_" + showPoints + "_" + timeLimit + "_" + minTime; } public byte[] render(){ @@ -512,11 +510,11 @@ public byte[] render(){ ResultSetWithTimes result; try { - result = AnalystProfileRequest.getResultWithTimes(surfaceId, shapefileId, attributeName); + result = AnalystProfileRequest.getResultWithTimes(surfaceId, shapefileId); } catch (NullPointerException e) { // not a profile request - result = AnalystRequest.getResultWithTimes(surfaceId, shapefileId, attributeName); + result = AnalystRequest.getResultWithTimes(surfaceId, shapefileId); } List features = shp.query(tile.envelope); diff --git a/conf/routes b/conf/routes index ab05bdc..6207dfd 100644 --- a/conf/routes +++ b/conf/routes @@ -52,7 +52,7 @@ DELETE /api/query/:id controllers.Api.deleteQuery(id:String) GET /tile/shapefile controllers.Tiles.shape(shapefileId:String, x:Integer, y:Integer, z:Integer, attributeName:String ?= null) GET /tile/spatial controllers.Tiles.spatial(shapefileId:String, x:Integer, y:Integer, z:Integer, selectedAttributes:String ) -GET /tile/surface controllers.Tiles.surface(surfaceId:Integer, shapefileId:String, attributeName:String, x:Integer, y:Integer, z:Integer, showIso:Boolean ?= false, showPoints:Boolean ?= true, timeLimit:Integer ?= 3600, minTime:Integer ?= null) +GET /tile/surface controllers.Tiles.surface(surfaceId:Integer, shapefileId:String, x:Integer, y:Integer, z:Integer, showIso:Boolean ?= false, showPoints:Boolean ?= true, timeLimit:Integer ?= 3600, minTime:Integer ?= null) GET /tile/query controllers.Tiles.query(queryId:String, x:Integer, y:Integer, z:Integer, timeLimit:Integer ?= 3600, weightByShapefile:String ?= null, weightByAttribute:String ?= null, groupBy:String ?= null, which: String, compareTo:String ?= null) GET /tile/transit controllers.Tiles.transit(scenarioId:String, x:Integer, y:Integer, z:Integer) @@ -60,14 +60,14 @@ GET /tile/transitComparison controllers.Tiles.transitComparison(scen GET /tile/surfaceComparison controllers.Tiles.surfaceComparison(surfaceId1:Integer, surfaceId2:Integer, shapefileId:String, attributeName:String, x:Integer, y:Integer, z:Integer, showIso:Boolean ?= false, showPoints:Boolean ?= false, timeLimit:Integer ?= 3600, minTime:Integer ?= null) GET /gis/query controllers.Gis.query(queryId:String, timeLimit:Integer ?= 3600, weightByShapefile:String ?= null, weightByAttribute:String ?= null, groupBy:String ?= null, which: String, compareTo:String ?= null) -GET /gis/surface controllers.Gis.surface(surfaceId:Integer, shapefileId:String, attributeName:String, timeLimit:Integer ?= 3600, compareTo:String ?= null) +GET /gis/surface controllers.Gis.surface(surfaceId:Integer, shapefileId:String, timeLimit:Integer ?= 3600, compareTo:String ?= null) GET /api/queryBins controllers.Api.queryBins(queryId:String, timeLimit:Integer ?= 3600, weightByShapefile:String ?= null, weightByAttribute:String ?= null, groupBy:String ?= null, which: String, compareTo: String ?= null) GET /api/surface controllers.Api.surface(graphId:String, lat:Double, lon:Double, mode:String, bikeSpeed:Double, walkSpeed:Double, which:String, date:String, fromTime:Integer, toTime:Integer ?= -1) GET /api/isochrone controllers.Api.isochrone(surfaceId:Integer, cutoffs:java.util.List[Integer]) -GET /api/result controllers.Api.result(surfaceId:Integer, shapefileId:String, attributeName:String) +GET /api/result controllers.Api.result(surfaceId:Integer, shapefileId:String) # Map static resources from the /public folder to the /assets URL path diff --git a/public/javascripts/analysis-single.js b/public/javascripts/analysis-single.js index 5ba3923..c44c2b7 100644 --- a/public/javascripts/analysis-single.js +++ b/public/javascripts/analysis-single.js @@ -12,7 +12,7 @@ var Analyst = Analyst || {}; 'change #scenario2': 'createSurface', 'change #shapefile': 'createSurface', 'cahnge .timesel': 'createSurface', - 'change #shapefileColumn': 'createSurface', + 'change #shapefileColumn': 'updateCharts', 'change #chartType' : 'updateResults', 'change .which input' : 'updateEnvelope', 'change #shapefile' : 'updateAttributes', @@ -380,41 +380,54 @@ var Analyst = Analyst || {}; this.$('#queryProcessing').hide(); this.$('#queryResults').show(); - var categoryId = this.shapefiles.get(this.$("#shapefile").val()).getCategoryId(); - var attributeId = this.$('#shapefileColumn').val() + var df, df1, df2; + df = df1 = df2 = null; if(this.comparisonType == 'compare') { + var res1Url = '/api/result?shapefileId=' + this.$("#shapefile").val() + + '&surfaceId=' + this.surfaceId1 + '&which=' + this.$('input[name="which"]:checked').val(); - if(this.surfaceId1) { - var resUrl = '/api/result?shapefileId=' + this.$("#shapefile").val() + '&attributeName=' + attributeId + - '&surfaceId=' + this.surfaceId1 + '&which=' + this.$('input[name="which"]:checked').val(); - - $.getJSON(resUrl, function(res) { - _this.scenario1Data = res; - _this.drawChart(res, categoryId + '.' + attributeId, 1, "#barChart1", 175); - - }); - } + df1 = $.get(res1Url); + df1.then(function (res) { + _this.scenario1Data = res; + }); - if(this.surfaceId2) { - var resUrl = '/api/result?shapefileId=' + this.$("#shapefile").val() + '&attributeName=' + attributeId + - '&surfaceId=' + this.surfaceId2 + '&which=' + this.$('input[name="which"]:checked').val(); - $.getJSON(resUrl, function(res) { + var res2Url = '/api/result?shapefileId=' + this.$("#shapefile").val() + + '&surfaceId=' + this.surfaceId2 + '&which=' + this.$('input[name="which"]:checked').val(); - _this.scenario2Data = res; - _this.drawChart(res, categoryId + '.' + attributeId, 2, "#barChart2", 175); + df2 = $.get(res2Url); + df2.then(function(res) { + _this.scenario2Data = res; + }); - }); - } + df = $.when(df1, df2); } else { - var resUrl = '/api/result?shapefileId=' + this.$("#shapefile").val() + '&attributeName=' + attributeId + + var resUrl = '/api/result?shapefileId=' + this.$("#shapefile").val() + '&surfaceId=' + this.surfaceId1 + '&which=' + this.$('input[name="which"]:checked').val(); - $.getJSON(resUrl, function(res) { + df = $.get(resUrl) + df.then(function(res) { + _this.scenario1Data = res; + _this.scenario2Data = null; + }); + } - _this.drawChart(res, categoryId + '.' + attributeId, 1, "#barChart1", 175); + df.then(function () { + _this.updateCharts(); + }); + }, - }); + /** + * Draw the charts + */ + updateCharts: function () { + var categoryId = this.shapefiles.get(this.$("#shapefile").val()).getCategoryId(); + var attributeId = this.$('#shapefileColumn').val() + + this.drawChart(this.scenario1Data, categoryId + '.' + attributeId, 1, '#barChart1', 175); + + if (this.scenario2Data) { + this.drawChart(this.scenario1Data, categoryId + '.' + attributeId, 2, '#barChart2', 175); } }, @@ -485,7 +498,7 @@ var Analyst = Analyst || {}; if(A.map.tileOverlay && A.map.hasLayer(A.map.tileOverlay)) A.map.removeLayer(A.map.tileOverlay); - A.map.tileOverlay = L.tileLayer('/tile/surfaceComparison?z={z}&x={x}&y={y}&shapefileId=' + this.$('#shapefile').val() + '&attributeName=' + this.$('#shapefileColumn').val() + + A.map.tileOverlay = L.tileLayer('/tile/surfaceComparison?z={z}&x={x}&y={y}&shapefileId=' + this.$('#shapefile').val() + '&minTime=' + minTime + '&timeLimit=' + timeLimit + '&surfaceId1=' + this.surfaceId1 + '&surfaceId2=' + this.surfaceId2 + '&showIso=' + showIso + '&showPoints=' + showPoints, {} ).addTo(A.map); @@ -495,7 +508,7 @@ var Analyst = Analyst || {}; if(A.map.tileOverlay && A.map.hasLayer(A.map.tileOverlay)) A.map.removeLayer(A.map.tileOverlay); - A.map.tileOverlay = L.tileLayer('/tile/surface?z={z}&x={x}&y={y}&shapefileId=' + this.$('#shapefile').val() + '&attributeName=' + this.$('#shapefileColumn').val() + + A.map.tileOverlay = L.tileLayer('/tile/surface?z={z}&x={x}&y={y}&shapefileId=' + this.$('#shapefile').val() + '&minTime=' + minTime + '&timeLimit=' + timeLimit + '&showPoints=' + showPoints + '&showIso=' + showIso + '&surfaceId=' + this.surfaceId1, { }).addTo(A.map); @@ -509,7 +522,7 @@ var Analyst = Analyst || {}; var surfaceId = this.surfaceId1; var timeLimit = this.timeSlider.getValue()[1] * 60; - window.location.href = '/gis/surface?shapefileId=' + shapefileId + '&surfaceId=' + surfaceId + '&attributeName=' + attributeName + '&timeLimit=' + timeLimit; + window.location.href = '/gis/surface?shapefileId=' + shapefileId + '&surfaceId=' + surfaceId + '&timeLimit=' + timeLimit; }, From 5bc8aa057bbf1f244e8e5a0182fedb12d52a6b7d Mon Sep 17 00:00:00 2001 From: Matthew Wigginton Conway Date: Tue, 6 Jan 2015 16:19:35 -0500 Subject: [PATCH 04/29] Multipoint with complete pointsets. --- app/controllers/Api.java | 10 ++-- app/controllers/Gis.java | 10 ++-- app/controllers/Tiles.java | 7 +-- app/models/Attribute.java | 2 +- app/models/Query.java | 20 ++------ app/models/Shapefile.java | 10 +++- app/tiles/AnalystTileRequest.java | 24 ++++++---- app/utils/QueryResults.java | 7 +-- conf/routes | 6 +-- public/javascripts/analysis-multi.js | 46 +++++++------------ public/javascripts/analysis-single.js | 27 +++-------- public/javascripts/models.js | 13 +----- .../analysis/analysis-multi-point.html | 5 -- .../templates/analysis/query-list-item.html | 5 ++ 14 files changed, 76 insertions(+), 116 deletions(-) diff --git a/app/controllers/Api.java b/app/controllers/Api.java index 2741238..a641bf1 100644 --- a/app/controllers/Api.java +++ b/app/controllers/Api.java @@ -330,7 +330,7 @@ public static List getIsochronesAccumulative(TimeSurface surf, Li } public static Result queryBins(String queryId, Integer timeLimit, String weightByShapefile, String weightByAttribute, String groupBy, - String which, String compareTo) { + String which, String attributeName, String compareTo) { response().setHeader(CACHE_CONTROL, "no-cache, no-store, must-revalidate"); response().setHeader(PRAGMA, "no-cache"); @@ -360,13 +360,13 @@ public static Result queryBins(String queryId, Integer timeLimit, String weightB try { - String queryKey = queryId + "_" + timeLimit + "_" + which; + String queryKey = queryId + "_" + timeLimit + "_" + which + "_" + attributeName; QueryResults qr = null; synchronized(QueryResults.queryResultsCache) { if(!QueryResults.queryResultsCache.containsKey(queryKey)) { - qr = new QueryResults(query, timeLimit, whichEnum); + qr = new QueryResults(query, timeLimit, whichEnum, attributeName); QueryResults.queryResultsCache.put(queryKey, qr); } else @@ -376,9 +376,9 @@ public static Result queryBins(String queryId, Integer timeLimit, String weightB if (otherQuery != null) { QueryResults otherQr = null; - queryKey = compareTo + "_" + timeLimit + "_" + which; + queryKey = compareTo + "_" + timeLimit + "_" + which + "_" + attributeName; if (!QueryResults.queryResultsCache.containsKey(queryKey)) { - otherQr = new QueryResults(otherQuery, timeLimit, whichEnum); + otherQr = new QueryResults(otherQuery, timeLimit, whichEnum, attributeName); QueryResults.queryResultsCache.put(queryKey, otherQr); } else { diff --git a/app/controllers/Gis.java b/app/controllers/Gis.java index d7a7ecb..12e2cc6 100644 --- a/app/controllers/Gis.java +++ b/app/controllers/Gis.java @@ -81,7 +81,7 @@ public class Gis extends Controller { static File TMP_PATH = new File("tmp/"); public static Result query(String queryId, Integer timeLimit, String weightByShapefile, String weightByAttribute, - String groupBy, String which, String compareTo) { + String groupBy, String which, String attributeName, String compareTo) { response().setHeader(CACHE_CONTROL, "no-cache, no-store, must-revalidate"); response().setHeader(PRAGMA, "no-cache"); @@ -112,7 +112,7 @@ public static Result query(String queryId, Integer timeLimit, String weightBySha synchronized(QueryResults.queryResultsCache) { if(!QueryResults.queryResultsCache.containsKey(queryKey)) { - qr = new QueryResults(query, timeLimit, whichEnum); + qr = new QueryResults(query, timeLimit, whichEnum, attributeName); QueryResults.queryResultsCache.put(queryKey, qr); } else @@ -122,7 +122,7 @@ public static Result query(String queryId, Integer timeLimit, String weightBySha String q2key = compareTo + "_" + timeLimit + "_" + which; if(!QueryResults.queryResultsCache.containsKey(q2key)) { - qr2 = new QueryResults(query2, timeLimit, whichEnum); + qr2 = new QueryResults(query2, timeLimit, whichEnum, attributeName); QueryResults.queryResultsCache.put(q2key, qr2); } else { @@ -257,10 +257,6 @@ public static Result surface(Integer surfaceId, String shapefileId, Integer time ArrayList gisFeatures = new ArrayList(); for(ShapeFeature feature : features) { - - - Integer sampleTime = result.getTime(feature.id); - GisShapeFeature gf = new GisShapeFeature(); gf.geom = feature.geom; gf.id = feature.id; diff --git a/app/controllers/Tiles.java b/app/controllers/Tiles.java index 9625a47..1c78420 100644 --- a/app/controllers/Tiles.java +++ b/app/controllers/Tiles.java @@ -124,7 +124,8 @@ public static Promise surfaceComparison(Integer surfaceId1, Integer surf } public static Promise query(String queryId, Integer x, Integer y, Integer z, - Integer timeLimit, String weightByShapefile, String weightByAttribute, String groupBy, String which, String compareTo) { + Integer timeLimit, String weightByShapefile, String weightByAttribute, String groupBy, + String which, String attributeName, String compareTo) { ResultEnvelope.Which whichEnum; try { @@ -142,9 +143,9 @@ public Result apply() throws Throwable { AnalystTileRequest tileRequest; if (compareTo == null) - tileRequest = new QueryTile(queryId, x, y, z, timeLimit, weightByShapefile, weightByAttribute, groupBy, whichEnum); + tileRequest = new QueryTile(queryId, x, y, z, timeLimit, weightByShapefile, weightByAttribute, groupBy, whichEnum, attributeName); else - tileRequest = new QueryComparisonTile(queryId, compareTo, x, y, z, timeLimit, weightByShapefile, weightByAttribute, groupBy, whichEnum); + tileRequest = new QueryComparisonTile(queryId, compareTo, x, y, z, timeLimit, weightByShapefile, weightByAttribute, groupBy, whichEnum, attributeName); return tileBuilder(tileRequest); } diff --git a/app/models/Attribute.java b/app/models/Attribute.java index 9054f3c..38c7d80 100644 --- a/app/models/Attribute.java +++ b/app/models/Attribute.java @@ -57,7 +57,7 @@ else if(f instanceof Long) * Sanitize a name for use as a category ID. Also implemented in the client: * A.models.Shapefile.getCategoryName() */ - static String convertNameToId(String name) { + public static String convertNameToId(String name) { return name.toLowerCase().trim().replaceAll(" ", "_").replaceAll("\\W",""); } diff --git a/app/models/Query.java b/app/models/Query.java index a8155e1..a629582 100644 --- a/app/models/Query.java +++ b/app/models/Query.java @@ -69,8 +69,6 @@ public class Query implements Serializable { public String mode; public String shapefileId; - - public String attributeName; public String scenarioId; public String status; @@ -123,18 +121,6 @@ public Boolean isTransit () { return new TraverseModeSet(this.mode).isTransit(); } - /** - * What attribute is this associated with? - */ - public Attribute getAttribute () { - Shapefile l = Shapefile.getShapefile(shapefileId); - - if (l == null) - return null; - - return l.attributes.get(attributeName); - } - public void save() { // assign id at save @@ -281,17 +267,19 @@ public void onReceive(Object message) throws Exception { JobSpec js; + String pointSetId = sl.id + ".json"; + if (q.isTransit()) { // create a profile request ProfileRequest pr = Api.analyst.buildProfileRequest(q.mode, q.date, q.fromTime, q.toTime, null); // the pointset is already in the cluster cache, from when it was uploaded. // every pointset has all shapefile attributes. - js = new JobSpec(q.scenarioId, sl.id, sl.id, pr); + js = new JobSpec(q.scenarioId, pointSetId, pointSetId, pr); } else { // this is not a transit request, no need for computationally-expensive profile routing RoutingRequest rr = Api.analyst.buildRequest(q.scenarioId, q.date, q.fromTime, null, q.mode, 120); - js = new JobSpec(q.scenarioId, sl.id, sl.id, rr); + js = new JobSpec(q.scenarioId, pointSetId, pointSetId, rr); } // plus a callback that registers how many work items have returned diff --git a/app/models/Shapefile.java b/app/models/Shapefile.java index 839487e..2606604 100644 --- a/app/models/Shapefile.java +++ b/app/models/Shapefile.java @@ -82,6 +82,12 @@ public class Shapefile implements Serializable { public String id; public String name; + + /** + * The name of this shapefile in the pointset. Don't change. + */ + public String categoryId; + public String description; public String filename; @@ -245,8 +251,6 @@ public synchronized PointSet getPointSet() { pointSet = new PointSet(getFeatureCount()); - String categoryId = Attribute.convertNameToId(this.name); - pointSet.id = categoryId; pointSet.label = this.name; pointSet.description = this.description; @@ -406,6 +410,8 @@ public static Shapefile create(File originalShapefileZip, String projectId, Stri shapefile.projectId = projectId; shapefile.name = name; + shapefile.categoryId = Attribute.convertNameToId(name); + ZipFile zipFile = new ZipFile(originalShapefileZip); diff --git a/app/tiles/AnalystTileRequest.java b/app/tiles/AnalystTileRequest.java index 1ff62b4..fa3260c 100644 --- a/app/tiles/AnalystTileRequest.java +++ b/app/tiles/AnalystTileRequest.java @@ -588,11 +588,13 @@ public static class QueryTile extends AnalystTileRequest { final Integer timeLimit; final String weightByShapefile; final String weightByAttribute; + final String attributeName; final String groupBy; final ResultEnvelope.Which which; public QueryTile(String queryId, Integer x, Integer y, Integer z, Integer timeLimit, - String weightByShapefile, String weightByAttribute, String groupBy, ResultEnvelope.Which which) { + String weightByShapefile, String weightByAttribute, String groupBy, + ResultEnvelope.Which which, String attributeName) { super(x, y, z, "transit"); this.queryId = queryId; @@ -601,10 +603,11 @@ public QueryTile(String queryId, Integer x, Integer y, Integer z, Integer timeLi this.weightByAttribute = weightByAttribute; this.groupBy = groupBy; this.which = which; + this.attributeName = attributeName; } public String getId() { - return super.getId() + "_" + queryId + "_" + timeLimit + "_" + which + "_" + weightByShapefile + "_" + groupBy + "_" + weightByAttribute; + return super.getId() + "_" + queryId + "_" + timeLimit + "_" + which + "_" + weightByShapefile + "_" + groupBy + "_" + weightByAttribute + "_" + attributeName; } public byte[] render(){ @@ -615,13 +618,13 @@ public byte[] render(){ return null; - String queryKey = queryId + "_" + timeLimit + "_" + which; + String queryKey = queryId + "_" + timeLimit + "_" + which + "_" + attributeName; QueryResults qr = null; synchronized(QueryResults.queryResultsCache) { if(!QueryResults.queryResultsCache.containsKey(queryKey)) { - qr = new QueryResults(query, timeLimit, which); + qr = new QueryResults(query, timeLimit, which, attributeName); QueryResults.queryResultsCache.put(queryKey, qr); } else @@ -724,8 +727,9 @@ public static class QueryComparisonTile extends QueryTile { public final String compareTo; public QueryComparisonTile(String queryId, String compareTo, Integer x, Integer y, Integer z, Integer timeLimit, - String weightByShapefile, String weightByAttribute, String groupBy, ResultEnvelope.Which which) { - super(queryId, x, y, z, timeLimit, weightByShapefile, weightByAttribute, groupBy, which); + String weightByShapefile, String weightByAttribute, String groupBy, ResultEnvelope.Which which, + String attributeName) { + super(queryId, x, y, z, timeLimit, weightByShapefile, weightByAttribute, groupBy, which, attributeName); this.compareTo = compareTo; } @@ -743,13 +747,13 @@ public byte[] render () { if (q1 == null || q2 == null || !q1.shapefileId.equals(q2.shapefileId)) return null; - String q1key = queryId + "_" + timeLimit + "_" + which; - String q2key = compareTo + "_" + timeLimit + "_" + which; + String q1key = queryId + "_" + timeLimit + "_" + which + "_" + attributeName; + String q2key = compareTo + "_" + timeLimit + "_" + which + "_" + attributeName; QueryResults qr1, qr2; if (!QueryResults.queryResultsCache.containsKey(q1key)) { - qr1 = new QueryResults(q1, timeLimit, which); + qr1 = new QueryResults(q1, timeLimit, which, attributeName); QueryResults.queryResultsCache.put(q1key, qr1); } else { @@ -757,7 +761,7 @@ public byte[] render () { } if (!QueryResults.queryResultsCache.containsKey(q2key)) { - qr2 = new QueryResults(q2, timeLimit, which); + qr2 = new QueryResults(q2, timeLimit, which, attributeName); QueryResults.queryResultsCache.put(q2key, qr2); } else { diff --git a/app/utils/QueryResults.java b/app/utils/QueryResults.java index 9955b5e..70d13dd 100644 --- a/app/utils/QueryResults.java +++ b/app/utils/QueryResults.java @@ -25,6 +25,7 @@ import com.vividsolutions.jts.index.SpatialIndex; import com.vividsolutions.jts.index.strtree.STRtree; +import models.Attribute; import models.Query; import models.Shapefile; import models.SpatialLayer; @@ -88,7 +89,7 @@ public QueryResults() { } - public QueryResults(Query q, Integer timeLimit, ResultEnvelope.Which which) { + public QueryResults(Query q, Integer timeLimit, ResultEnvelope.Which which, String attributeId) { Shapefile sd = Shapefile.getShapefile(q.shapefileId); this.which = which; @@ -116,7 +117,7 @@ public QueryResults(Query q, Integer timeLimit, ResultEnvelope.Which which) { throw new RuntimeException("Unhandled envelope type"); } - value = (double) feature.sum(timeLimit); + value = (double) feature.sum(timeLimit, sd.categoryId + "." + attributeId); if(maxValue == null || value > maxValue) maxValue = value; @@ -134,7 +135,7 @@ public QueryResults(Query q, Integer timeLimit, ResultEnvelope.Which which) { shapeFileId = sd.id; - attributeId = q.attributeName; + this.attributeId = attributeId; this.maxPossible = sd.attributes.get(attributeId).sum; diff --git a/conf/routes b/conf/routes index 6207dfd..ceefa55 100644 --- a/conf/routes +++ b/conf/routes @@ -53,17 +53,17 @@ GET /tile/shapefile controllers.Tiles.shape(shapefileId:Stri GET /tile/spatial controllers.Tiles.spatial(shapefileId:String, x:Integer, y:Integer, z:Integer, selectedAttributes:String ) GET /tile/surface controllers.Tiles.surface(surfaceId:Integer, shapefileId:String, x:Integer, y:Integer, z:Integer, showIso:Boolean ?= false, showPoints:Boolean ?= true, timeLimit:Integer ?= 3600, minTime:Integer ?= null) -GET /tile/query controllers.Tiles.query(queryId:String, x:Integer, y:Integer, z:Integer, timeLimit:Integer ?= 3600, weightByShapefile:String ?= null, weightByAttribute:String ?= null, groupBy:String ?= null, which: String, compareTo:String ?= null) +GET /tile/query controllers.Tiles.query(queryId:String, x:Integer, y:Integer, z:Integer, timeLimit:Integer ?= 3600, weightByShapefile:String ?= null, weightByAttribute:String ?= null, groupBy:String ?= null, which: String, attributeName:String, compareTo:String ?= null) GET /tile/transit controllers.Tiles.transit(scenarioId:String, x:Integer, y:Integer, z:Integer) GET /tile/transitComparison controllers.Tiles.transitComparison(scenarioId1:String, scenarioId2:String, x:Integer, y:Integer, z:Integer) GET /tile/surfaceComparison controllers.Tiles.surfaceComparison(surfaceId1:Integer, surfaceId2:Integer, shapefileId:String, attributeName:String, x:Integer, y:Integer, z:Integer, showIso:Boolean ?= false, showPoints:Boolean ?= false, timeLimit:Integer ?= 3600, minTime:Integer ?= null) -GET /gis/query controllers.Gis.query(queryId:String, timeLimit:Integer ?= 3600, weightByShapefile:String ?= null, weightByAttribute:String ?= null, groupBy:String ?= null, which: String, compareTo:String ?= null) +GET /gis/query controllers.Gis.query(queryId:String, timeLimit:Integer ?= 3600, weightByShapefile:String ?= null, weightByAttribute:String ?= null, groupBy:String ?= null, which: String, attributeName: String, compareTo:String ?= null) GET /gis/surface controllers.Gis.surface(surfaceId:Integer, shapefileId:String, timeLimit:Integer ?= 3600, compareTo:String ?= null) -GET /api/queryBins controllers.Api.queryBins(queryId:String, timeLimit:Integer ?= 3600, weightByShapefile:String ?= null, weightByAttribute:String ?= null, groupBy:String ?= null, which: String, compareTo: String ?= null) +GET /api/queryBins controllers.Api.queryBins(queryId:String, timeLimit:Integer ?= 3600, weightByShapefile:String ?= null, weightByAttribute:String ?= null, groupBy:String ?= null, which: String, attributeName: String, compareTo: String ?= null) GET /api/surface controllers.Api.surface(graphId:String, lat:Double, lon:Double, mode:String, bikeSpeed:Double, walkSpeed:Double, which:String, date:String, fromTime:Integer, toTime:Integer ?= -1) GET /api/isochrone controllers.Api.isochrone(surfaceId:Integer, cutoffs:java.util.List[Integer]) diff --git a/public/javascripts/analysis-multi.js b/public/javascripts/analysis-multi.js index d3279ba..205e8c3 100644 --- a/public/javascripts/analysis-multi.js +++ b/public/javascripts/analysis-multi.js @@ -9,8 +9,7 @@ var Analyst = Analyst || {}; events: { 'click #createQuery': 'createQuery', 'click #cancelQuery': 'cancelQuery', - 'click #newQuery': 'newQuery', - 'change #shapefile': 'updateAttributes' + 'click #newQuery': 'newQuery' }, regions: { @@ -18,7 +17,7 @@ var Analyst = Analyst || {}; }, initialize: function(options) { - _.bindAll(this, 'createQuery', 'cancelQuery', 'updateAttributes'); + _.bindAll(this, 'createQuery', 'cancelQuery'); }, onRender: function() { @@ -93,10 +92,8 @@ var Analyst = Analyst || {}; .attr('value', shp.id) .text(shp.get('name')) .appendTo(this.$('#shapefile')); - }); - - _this.updateAttributes(); }); + }); // pick a reasonable default date $.get('api/project/' + A.app.selectedProject + '/exemplarDay') @@ -130,26 +127,6 @@ var Analyst = Analyst || {}; this.main.show(queryListLayout); }, - /** - * Update the attributes select to show the attributes of the current shapefile - */ - updateAttributes: function () { - var shpId = this.$('#shapefile').val(); - var shp = this.shapefiles.get(shpId); - var _this = this; - - this.$('#shapefileColumn').empty(); - - shp.getNumericAttributes().forEach(function (attr) { - var atName = A.models.Shapefile.attributeName(attr); - - $('