diff --git a/geoscript/examples/plot/__init__.py b/geoscript/examples/plot/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/geoscript/examples/plot/box_population.py b/geoscript/examples/plot/box_population.py new file mode 100644 index 0000000..06d028c --- /dev/null +++ b/geoscript/examples/plot/box_population.py @@ -0,0 +1,10 @@ +from geoscript.plot.functions import attributesasdict +from geoscript.layer.shapefile import Shapefile +from geoscript.plot.box import box +from geoscript.filter import Filter + +if __name__ == '__main__': + layer = Shapefile('D:/gisdata/denver_shapefiles/census_boundaries.shp') + chart = box(attributesasdict(layer, ['BLACK', 'AMERI_ES', 'ASIAN', 'OTHER', 'HISPANIC'], Filter("COUNTYNAME = 'DENVER'"))) + chart.show() + \ No newline at end of file diff --git a/geoscript/examples/plot/correlation_attributes.py b/geoscript/examples/plot/correlation_attributes.py new file mode 100644 index 0000000..336ea1c --- /dev/null +++ b/geoscript/examples/plot/correlation_attributes.py @@ -0,0 +1,8 @@ +from geoscript.processing.sampledata import sampledata +from geoscript.plot import attribute +from geoscript.plot.regression import linearregression + +if __name__ == '__main__': + layer = sampledata.get('heights') + chart = linearregression(attribute(layer, 'VALUE'), attribute(layer, 'VALUE')) + chart.show() \ No newline at end of file diff --git a/geoscript/examples/plot/correlation_distance.py b/geoscript/examples/plot/correlation_distance.py new file mode 100644 index 0000000..96602af --- /dev/null +++ b/geoscript/examples/plot/correlation_distance.py @@ -0,0 +1,11 @@ +from geoscript.processing.sampledata import sampledata +from geoscript.plot import linearregression +from geoscript.plot import attribute +from geoscript.plot import disttopoint + +if __name__ == '__main__': + layer = sampledata.get('heights') + pt = layer.features().next().geom.centroid + chart = linearregression(disttopoint(layer, pt.x, pt.y), attribute(layer, 'VALUE')) + chart.savepng("d:\\chart.png") + chart.show() \ No newline at end of file diff --git a/geoscript/examples/plot/distance_distribution.py b/geoscript/examples/plot/distance_distribution.py new file mode 100644 index 0000000..1c0c7a8 --- /dev/null +++ b/geoscript/examples/plot/distance_distribution.py @@ -0,0 +1,36 @@ +from geoscript.plot import * +from geoscript.layer.shapefile import Shapefile +from geoscript.processing.sampledata import sampledata + +if __name__ == '__main__': + + #layer = Shapefile('/home/myuser/points.shp') + #=========================================================================== + # layer = sampledata.get('heights') + # x = y = ifeat = 0 + # for feature in layer.features(): + # pt = feature.geom.centroid + # x += pt.x + # y += pt.y + # ifeat += 1 + # x /= float(ifeat) + # y /= float(ifeat) + # chart = xybars(frequency(disttopoint(layer, x, y), 10)) + # chart.show() + #=========================================================================== + + #the same thing using the xy function to calculate the mean center + layer = sampledata.get('heights') + x = y = ifeat = 0 + xy = xy(layer) + for px,py in xy: + x += px + y += py + ifeat += 1 + x /= float(ifeat) + y /= float(ifeat) + chart = xybars(frequency(disttopoint(layer, x, y), 10)) + chart.show() + + + diff --git a/geoscript/examples/plot/multiple_bars.py b/geoscript/examples/plot/multiple_bars.py new file mode 100644 index 0000000..cf7f146 --- /dev/null +++ b/geoscript/examples/plot/multiple_bars.py @@ -0,0 +1,8 @@ +from geoscript.plot import * +from geoscript.layer.shapefile import Shapefile + +if __name__ == '__main__': + layer = Shapefile('D:/gisdata/denver_shapefiles/census_boundaries.shp') + chart = categorybars(summarize(layer, 'BLKGRP', ['BLACK', 'ASIAN', 'HISPANIC']), stacked=True) + chart.show() + \ No newline at end of file diff --git a/geoscript/examples/plot/overlay.py b/geoscript/examples/plot/overlay.py new file mode 100644 index 0000000..307873a --- /dev/null +++ b/geoscript/examples/plot/overlay.py @@ -0,0 +1,17 @@ +from geoscript.plot import * +import random + +'''An example that shows how plots can be overlayed''' + +if __name__ == '__main__': + xy = [(i,random.random() * 10) for i in range(10)] + chart = curve(xy) + xy2 = [(i,random.random() * 3) for i in range(10)] + chart2 = curve(xy2) + xy3 = [(i,random.random()) for i in range(10)] + chart3 = curve(xy3) + xy4 = [(random.random() * 10,random.random() * 10) for i in range(100)] + chart4 = scatterplot(xy4) + chart.overlay(chart2, chart3, chart4) + chart.show() + \ No newline at end of file diff --git a/geoscript/examples/plot/plot_points.py b/geoscript/examples/plot/plot_points.py new file mode 100644 index 0000000..f9eb02f --- /dev/null +++ b/geoscript/examples/plot/plot_points.py @@ -0,0 +1,9 @@ +from geoscript.plot import * +from geoscript.processing.sampledata import sampledata + +if __name__ == '__main__': + + layer = sampledata.get('heights') + chart = scatterplot(xy(layer)) + chart.show() + \ No newline at end of file diff --git a/geoscript/examples/plot/semivariogram.py b/geoscript/examples/plot/semivariogram.py new file mode 100644 index 0000000..491b79c --- /dev/null +++ b/geoscript/examples/plot/semivariogram.py @@ -0,0 +1,26 @@ +from geoscript.plot import * +from geoscript.processing.sampledata import sampledata + +'''An example that plots a semivariogram cloud''' +import math +import random + +if __name__ == '__main__': + layer = sampledata.get('heights') + semivar = [] + i= 0 + for feature in layer.features(): + pt1 = feature.geom.getCoordinates()[0] + value1 = feature.get('VALUE') + i2 = 0 + for feature2 in layer.features(): + if i2 > i: + pt2 = feature2.geom.getCoordinates()[0] + value2 = feature2.get('VALUE') + semivar.append((pt1.distance(pt2), math.pow(value2-value1,2.))) + i2 += 1 + i+=1 + # we do not plot all points, as that would mean a large + chart = scatterplot(semivar) + chart.show() + \ No newline at end of file diff --git a/geoscript/examples/plot/unique_barchart.py b/geoscript/examples/plot/unique_barchart.py new file mode 100644 index 0000000..87b9e9c --- /dev/null +++ b/geoscript/examples/plot/unique_barchart.py @@ -0,0 +1,7 @@ +from geoscript.processing.sampledata import sampledata +from geoscript.plot import * + +if __name__ == '__main__': + layer = sampledata.get('landcover') + chart = categorybars(uniquecounts(layer, 'LANDCOVER')) + chart.show() \ No newline at end of file diff --git a/geoscript/examples/plot/unique_pie.py b/geoscript/examples/plot/unique_pie.py new file mode 100644 index 0000000..fef8286 --- /dev/null +++ b/geoscript/examples/plot/unique_pie.py @@ -0,0 +1,7 @@ +from geoscript.processing.sampledata import sampledata +from geoscript.plot import * + +if __name__ == '__main__': + layer = sampledata.get('landcover') + chart = pie(uniquecounts(layer, 'LANDCOVER')) + chart.show() \ No newline at end of file diff --git a/geoscript/plot/__init__.py b/geoscript/plot/__init__.py new file mode 100644 index 0000000..34bc9ef --- /dev/null +++ b/geoscript/plot/__init__.py @@ -0,0 +1,8 @@ +from regression import linearregression, powregression +from bars import xybars, categorybars +from pie import pie +from box import box +from curve import curve +from scatterplot import scatterplot +from functions import attribute, attributes, attributesasdict, x, y, disttopoint,\ + uniquecounts, frequency, xy, summarize \ No newline at end of file diff --git a/geoscript/plot/bars.py b/geoscript/plot/bars.py new file mode 100644 index 0000000..1f868ce --- /dev/null +++ b/geoscript/plot/bars.py @@ -0,0 +1,57 @@ +from org.jfree.data.xy import XYSeriesCollection, XYSeries +from org.jfree.data.category import DefaultCategoryDataset +from org.jfree.chart import ChartFactory +from org.jfree.chart.plot import PlotOrientation +from geoscript.plot.chart import Chart + +def xybars(data, name="xybars"): + '''Creates a xy bars chart. + + Input is a list of tuples with (x,y) values''' + series = XYSeries(name); + for x,y in data: + series.add(x,y) + dataset = XYSeriesCollection(series) + chart = ChartFactory.createXYBarChart(None, "X", False,"Y", + dataset, PlotOrientation.VERTICAL, True, True, False) + return Chart(chart) + +def intervalbars(data, name="xybars"): + '''Creates a xy bars chart, whit bars defining intervals. + Input is a list of tuples in the form ((lo,hi),y), *lo* and *hi* being + the lower and upper limits of the interval and *y* the count on that interval''' + pass + +def categorybars(cat, name="", xlab="", ylab="", stacked=False, tridim=False): + '''Creates a barchart with categorical data. + + The input is a dict with category names as keys and numerical values + (the height of the bar) corresponding to that category as values. + + Multiple series can be plot, by passing a dict with series names as keys + and dicts as the one described above as values. + + By setting *stacked* to true the result will be a stacked bar chart''' + dataset = DefaultCategoryDataset(); + for k,v in cat.iteritems(): + if isinstance(v, dict): + for k2,v2 in v.iteritems(): + dataset.addValue(v2, k2, k) + else: + dataset.addValue(v, "", k) + + if tridim: + if stacked: + chart = ChartFactory.createStackedBarChart3D(name, xlab, ylab, dataset, + PlotOrientation.VERTICAL, True, True, True) + else: + chart = ChartFactory.createBarChart3D(name, xlab, ylab, dataset, + PlotOrientation.VERTICAL, True, True, True) + else: + if stacked: + chart = ChartFactory.createStackedBarChart(name, xlab, ylab, dataset, + PlotOrientation.VERTICAL, True, True, True) + else: + chart = ChartFactory.createBarChart(name, xlab, ylab, dataset, + PlotOrientation.VERTICAL, True, True, True) + return Chart(chart) \ No newline at end of file diff --git a/geoscript/plot/box.py b/geoscript/plot/box.py new file mode 100644 index 0000000..af74567 --- /dev/null +++ b/geoscript/plot/box.py @@ -0,0 +1,16 @@ +from org.jfree.data.statistics import DefaultBoxAndWhiskerCategoryDataset +from org.jfree.chart import ChartFactory +from geoscript.plot.chart import Chart + +def box(data): + '''creates a box and whiskers plot. + Data is passed as a dict with category names as keys and lists of numbers as values + ''' + dataset = DefaultBoxAndWhiskerCategoryDataset() + for name, values in data.iteritems(): + dataset.add(values, "", name); + chart = ChartFactory.createBoxAndWhiskerChart("", "", "", dataset, True) + return Chart(chart) + + + \ No newline at end of file diff --git a/geoscript/plot/chart.py b/geoscript/plot/chart.py new file mode 100644 index 0000000..26477b6 --- /dev/null +++ b/geoscript/plot/chart.py @@ -0,0 +1,37 @@ +from javax import swing +from org.jfree.chart import ChartPanel, ChartUtilities +from geoscript import util +from org.jfree.chart.plot import DatasetRenderingOrder +from org.jfree.chart.axis import NumberAxis + +class Chart(): + '''A class that wraps a JFreeChart Chart object and has method + to easily show or save it''' + + def __init__(self,chart): + self.chart = chart + self.datasets = 1 + + def show(self, size=(500,500)): + panel = ChartPanel(self.chart) + frame = swing.JFrame() + frame.setContentPane(panel) + frame.size = size + frame.visible = True + + def savepng(self, filename, size=(500,500)): + ChartUtilities.saveChartAsPNG(util.toFile(filename), self.chart, size[0], size[1]) + + def overlay(self, *charts): + for chart in charts: + self._overlay(chart) + + def _overlay(self, chart): + plot = self.chart.getPlot() + plot.setDataset(self.datasets, chart.chart.getPlot().getDataset()) + plot.setRenderer(self.datasets, chart.chart.getPlot().getRenderer()) + plot.setDatasetRenderingOrder(DatasetRenderingOrder.FORWARD) + yAxis = NumberAxis("") + plot.setRangeAxis(self.datasets, yAxis); + plot.mapDatasetToRangeAxis(self.datasets, self.datasets) + self.datasets += 1 \ No newline at end of file diff --git a/geoscript/plot/curve.py b/geoscript/plot/curve.py new file mode 100644 index 0000000..c0bcae0 --- /dev/null +++ b/geoscript/plot/curve.py @@ -0,0 +1,27 @@ +from org.jfree.data.xy import XYSeries, XYSeriesCollection +from org.jfree.chart.plot import PlotOrientation +from org.jfree.chart import ChartFactory +from geoscript.plot.chart import Chart +from org.jfree.chart.renderer.xy import XYSplineRenderer, XYLine3DRenderer + +def curve(data, name="", smooth=True, tridim=True): + '''Creates a curve based on a list of (x,y) tuples. + + If *smooth* is true, then it uses a spline renderer. + + If *tridim* is true, then it creates a 3d-like plot.In this case + smooth value is neglected and no smoothing is added''' + dataset = XYSeriesCollection() + xy = XYSeries(name); + for d in data: + xy.add(d[0], d[1]) + dataset.addSeries(xy); + chart = ChartFactory.createXYLineChart( + None, None, None, dataset, PlotOrientation.VERTICAL, True, True, False) + if smooth: + chart.getXYPlot().setRenderer(XYSplineRenderer()) + if tridim: + chart.getXYPlot().setRenderer(XYLine3DRenderer()) + + return Chart(chart) + \ No newline at end of file diff --git a/geoscript/plot/functions.py b/geoscript/plot/functions.py new file mode 100644 index 0000000..fab04d2 --- /dev/null +++ b/geoscript/plot/functions.py @@ -0,0 +1,125 @@ +''' +Functions in this class returns iterables than can be used as input for +any of the method in the plot package that create chart. + +This functions should simplify the creation of dataset when the data to +plot comes from a GeoScript layer or can be derived from it + +For now, all this functions work with vector layers, althought they might be adapted +to use also raster ones +''' + +from geoscript.geom.point import Point +from com.vividsolutions.jts.operation.distance import DistanceOp +import math + +def attribute(layer, attr, filter=None): + '''returns a generator with values of a given attribute of a given vector layer''' + for feature in layer.features(filter): + yield float(feature.get(attr)) + +def attributes(layer, attrs, filter=None): + '''returns a generator with values of a given set of attribute of a given vector layer. + The generator contain tuples with as many values as attribute names were passed''' + for feature in layer.features(filter): + yield tuple(float(feature.get(attr)) for attr in attrs) + +def attributesasdict(layer, attrs, filter=None): + '''returns a dict with attribute names as keys and lists of values for each parameter as values''' + ret = {} + for attr in attrs: + ret[attr] = [] + for feature in layer.features(filter): + for attr in attrs: + ret[attr].append(feature.get(attr)) + return ret + +def x(layer, filter=None): + '''returns a generator with x coordinates of features in a vector layer. + If the layer contains lines or polygons, the x coordinate of the centroid is used''' + for feature in layer.features(filter): + yield feature.geom.centroid.x + +def y(layer, filter=None): + '''returns a generator with y coordinates of features in a vector layer. + If the layer contains lines or polygons, the y coordinate of the centroid is used''' + for feature in layer.features(filter): + yield feature.geom.centroid.x + +def xy(layer, filter=None): + '''returns a generator with x coordinates of features in a vector layer. + If the layer contains lines or polygons, the x coordinate of the centroid is used''' + for feature in layer.features(filter): + centroid = feature.geom.centroid + yield (centroid.x, centroid.y) + + +def disttopoint(layer, x, y, filter=None): + '''returns a generator with distances from features in a vector layer to a given point''' + pt = Point(x,y) + for feature in layer.features(filter): + yield DistanceOp(pt, feature.geom).distance() + +def uniquecounts(layer, attr, filter=None): + '''returns a dict with unique values as keys, and counts(number of times that unique value appears + in the passed layer) as values''' + categories = {} + for feature in layer.features(filter): + value = feature.get(attr) + if value not in categories.keys(): + categories[value] = 1 + categories[value] += 1 + return categories + + +def frequency(data, nbins): + '''returns a frequency distribution of a data series, divided in a given number of bins. + the distribution is returned as a list of tuples in the form (bin_center, frequency)''' + datalist = list(data) + lo = min(datalist) + hi = max(datalist) + binsize = (hi-lo)/(float(nbins)) + freq = [0] * nbins + for d in datalist: + bin = int(math.ceil((d - lo)/binsize) - 1) + freq[bin] += 1 + ret = [] + i = 0 + for f in freq: + center = binsize / 2 + binsize * i + ret.append((center,f)) + i += 1 + return ret + + +def summarize(layer, keyattr, attrs, op='sum', filter=None): + '''Summarizes a layer according to the classes defined by a given attribute. + Operation to performed has to be selected from 'sum', 'max, 'min', 'avg' + Attributes to compute the selected operation on are passed as a list with attribute names. + Returns a dict with category values as keys, and another dict as values. This second dict + has attribute names as keys and the selected statistical value fr each attribute as values''' + categories = {} + count = 0 + for feature in layer.features(filter): + cat = feature.get(keyattr) + if cat not in categories.keys(): + categories[cat] = {} + for attr in attrs: + categories[cat][attr] = 0 + for attr in attrs: + value = feature.get(attr) + if op == 'sum' or op == 'mean': + categories[cat][attr] += value + if op == 'min': + categories[cat][attr] = min(value, categories[cat][attr]) + if op == 'max': + categories[cat][attr] = max(value, categories[cat][attr]) + count += 1 + + if op == 'mean': + for cat in categories.keys(): + for attr in categories[cat].keys(): + categories[cat][attr] = categories[cat][attr]/float(count) + + return categories + \ No newline at end of file diff --git a/geoscript/plot/pie.py b/geoscript/plot/pie.py new file mode 100644 index 0000000..86fe0dd --- /dev/null +++ b/geoscript/plot/pie.py @@ -0,0 +1,13 @@ +from org.jfree.data.general import DefaultPieDataset +from org.jfree.chart import ChartFactory +from geoscript.plot.chart import Chart + +def pie(data, name="", tridim=False): + dataset = DefaultPieDataset(); + for k,v in data.iteritems(): + dataset.setValue(k, v) + if tridim: + chart = ChartFactory.createPieChart3D(name,dataset, True, True, False) + else: + chart = ChartFactory.createPieChart(name,dataset, True, True, False) + return Chart(chart) \ No newline at end of file diff --git a/geoscript/plot/regression.py b/geoscript/plot/regression.py new file mode 100644 index 0000000..b85fe2d --- /dev/null +++ b/geoscript/plot/regression.py @@ -0,0 +1,65 @@ +from org.jfree.data.xy import XYSeriesCollection, XYSeries +from org.jfree.chart.renderer.xy import XYLineAndShapeRenderer, XYDotRenderer +from java.awt import Color +from org.jfree.chart import JFreeChart +from org.jfree.chart.plot import XYPlot +from org.jfree.chart.axis import NumberAxis +import itertools +from org.jfree.data.statistics import Regression +from org.jfree.data.general import DatasetUtilities +from org.jfree.data.function import LineFunction2D, PowerFunction2D +from geoscript.plot.chart import Chart + +def linearregression(X, Y): + return regression(X, Y, 0) + +def powregression(X, Y): + return regression(X, Y, 1) + + +def regression(X,Y, regtype = 0): + + xAxis = NumberAxis("X") + xAxis.setAutoRangeIncludesZero(False) + yAxis = NumberAxis("Y") + yAxis.setAutoRangeIncludesZero(False) + + series = XYSeries("Values"); + xmax = xmin = None + for (x,y) in itertools.izip(X, Y): + series.add(x, y); + if xmax is None: + xmax = xmin = x + else: + xmax = max(xmax, x) + xmin = min(xmin, x) + + dataset = XYSeriesCollection() + dataset.addSeries(series); + renderer1 = XYDotRenderer() + plot = XYPlot(dataset, xAxis, yAxis, renderer1) + + if regtype == 1: + coefficients = Regression.getPowerRegression(dataset, 0) + curve = PowerFunction2D(coefficients[0], coefficients[1]) + regdesc = "Power Regression" + else: + coefficients = Regression.getOLSRegression(dataset, 0) + curve = LineFunction2D(coefficients[0], coefficients[1]) + regdesc = "Linear Regression" + + regressionData = DatasetUtilities.sampleFunction2D(curve, + xmin, xmax, 100, "Fitted Regression Line") + + plot.setDataset(1, regressionData) + renderer2 = XYLineAndShapeRenderer(True, False) + renderer2.setSeriesPaint(0, Color.blue) + plot.setRenderer(1, renderer2) + + jfchart = JFreeChart(regdesc, JFreeChart.DEFAULT_TITLE_FONT, plot, True); + + chart = Chart(jfchart) + chart.coeffs = coefficients + + return chart + \ No newline at end of file diff --git a/geoscript/plot/scatterplot.py b/geoscript/plot/scatterplot.py new file mode 100644 index 0000000..7a3d455 --- /dev/null +++ b/geoscript/plot/scatterplot.py @@ -0,0 +1,35 @@ +from org.jfree.data.xy import XYSeriesCollection, XYSeries +from org.jfree.chart import ChartFactory +from org.jfree.chart.plot import PlotOrientation +from org.jfree.chart.axis import NumberAxis +import itertools +from geoscript.plot.chart import Chart +from org.jfree.util import ShapeUtilities + +def scatterplot(X,Y=None, name="", xlab="", ylab="", size= 3): + '''creates a scatterplot from x and y data. + Data can be passed as a list of (x,y) tuples or two lists with + x and y values''' + + xAxis = NumberAxis(xlab) + xAxis.setAutoRangeIncludesZero(False) + yAxis = NumberAxis(ylab) + yAxis.setAutoRangeIncludesZero(False) + + series = XYSeries("Values"); + if Y is not None: + iterable = itertools.izip(X, Y) + else: + iterable = X + for (x,y) in iterable: + series.add(x, y); + + dataset = XYSeriesCollection() + dataset.addSeries(series); + chart = ChartFactory.createScatterPlot(name, xlab, ylab, dataset,\ + PlotOrientation.VERTICAL, True, True, False) + plot = chart.getPlot() + plot.getRenderer().setSeriesShape(0, ShapeUtilities.createRegularCross(size,size)); + + return Chart(chart) + \ No newline at end of file