mirror of
https://github.com/meteoinfo/MeteoInfo.git
synced 2025-12-08 20:36:05 +00:00
635 lines
20 KiB
Python
635 lines
20 KiB
Python
# -----------------------------------------------------
|
|
# Author: Yaqiang Wang
|
|
# Date: 2015-9-20
|
|
# Purpose: MeteoInfoLab layer module
|
|
# Note: Jython
|
|
# -----------------------------------------------------
|
|
import geoutil
|
|
import mipylib.miutil as miutil
|
|
import mipylib.numeric as np
|
|
from java.awt import Font
|
|
from java.time import LocalDateTime
|
|
|
|
from org.meteoinfo.data import TableUtil, XYListDataset
|
|
from org.meteoinfo.geo.layer import LayerTypes, VectorLayer
|
|
from org.meteoinfo.geometry.legend import LegendType
|
|
from org.meteoinfo.projection import ProjectionUtil, KnownCoordinateSystems
|
|
from org.meteoinfo.geometry.shape import PolygonShape, ShapeTypes
|
|
from org.meteoinfo.geo.analysis import GeometryUtil
|
|
from org.meteoinfo.geo.util import GeoProjectionUtil
|
|
from org.meteoinfo.geo.io import GeoJSONReader, GeoJSONWriter
|
|
|
|
|
|
class MILayer(object):
|
|
"""
|
|
Map layer
|
|
|
|
:param layer: (*MapLayer*) MapLayer object.
|
|
:param shapetype: (*ShapeTypes*) Shape type ['point' | 'point_z' | 'line' | 'line_z' | 'polygon'
|
|
| 'polygon_z']
|
|
"""
|
|
|
|
def __init__(self, layer=None, shapetype=None):
|
|
if layer is None:
|
|
if shapetype is None:
|
|
print('shapetype must be specified!')
|
|
else:
|
|
shapetype = shapetype.upper()
|
|
if shapetype == 'LINE':
|
|
shapetype = 'POLYLINE'
|
|
elif shapetype == 'LINE_Z':
|
|
shapetype = 'POLYLINE_Z'
|
|
try:
|
|
type = ShapeTypes.valueOf(shapetype)
|
|
except:
|
|
print('shapetype is not valid: {}'.foramt(shapetype))
|
|
type = ShapeTypes.POINT
|
|
self._layer = VectorLayer(type)
|
|
self.shapetype = type
|
|
self.proj = KnownCoordinateSystems.geographic.world.WGS1984
|
|
else:
|
|
self._layer = layer
|
|
self.shapetype = layer.getShapeType()
|
|
self.proj = layer.getProjInfo()
|
|
self._coord_array = None
|
|
|
|
def __repr__(self):
|
|
return self._layer.getLayerInfo()
|
|
|
|
@property
|
|
def x_coord(self):
|
|
"""
|
|
Get X coordinate array.
|
|
:return: X coordinate array
|
|
"""
|
|
if self.isvectorlayer():
|
|
if self._coord_array is None:
|
|
self._coord_array = GeometryUtil.getCoordinates(self._layer)
|
|
return np.array(self._coord_array[0])
|
|
else:
|
|
return None
|
|
|
|
@property
|
|
def y_coord(self):
|
|
"""
|
|
Get Y coordinate array.
|
|
:return: Y coordinate array
|
|
"""
|
|
if self.isvectorlayer():
|
|
if self._coord_array is None:
|
|
self._coord_array = GeometryUtil.getCoordinates(self._layer)
|
|
return np.array(self._coord_array[1])
|
|
else:
|
|
return None
|
|
|
|
@property
|
|
def z_coord(self):
|
|
"""
|
|
Get Z coordinate array.
|
|
:return: Z coordinate array
|
|
"""
|
|
if self.isvectorlayer():
|
|
if self._coord_array is None:
|
|
self._coord_array = GeometryUtil.getCoordinates(self._layer)
|
|
return np.array(self._coord_array[2])
|
|
else:
|
|
return None
|
|
|
|
@property
|
|
def m_coord(self):
|
|
"""
|
|
Get M coordinate array.
|
|
:return: M coordinate array
|
|
"""
|
|
if self.isvectorlayer():
|
|
if self._coord_array is None:
|
|
self._coord_array = GeometryUtil.getCoordinates(self._layer)
|
|
return np.array(self._coord_array[3])
|
|
else:
|
|
return None
|
|
|
|
def isvectorlayer(self):
|
|
"""
|
|
Check this layer is VectorLayer or not.
|
|
|
|
:returns: (*boolean*) Is VectorLayer or not.
|
|
"""
|
|
return self._layer.getLayerType() == LayerTypes.VECTOR_LAYER
|
|
|
|
def get_encoding(self):
|
|
"""
|
|
Get encoding.
|
|
|
|
:returns: (*string*) Encoding
|
|
"""
|
|
return self._layer.getAttributeTable().getEncoding()
|
|
|
|
@property
|
|
def datatable(self):
|
|
"""
|
|
Get attribute table.
|
|
|
|
:return: Attribute table.
|
|
"""
|
|
r = self._layer.getAttributeTable().getTable()
|
|
return np.datatable(r)
|
|
|
|
def gettable(self):
|
|
"""
|
|
Get attribute table.
|
|
|
|
:returns: (*PyTableData') Attribute table.
|
|
"""
|
|
r = self._layer.getAttributeTable().getTable()
|
|
return np.datatable(r)
|
|
|
|
def cellvalue(self, fieldname, shapeindex):
|
|
"""
|
|
Get attribute table cell value.
|
|
|
|
:param fieldname: (*string*) Field name.
|
|
:param shapeindex: (*int*) Shape index.
|
|
|
|
:returns: The value in attribute table identified by field name and shape index.
|
|
"""
|
|
v = self._layer.getCellValue(fieldname, shapeindex)
|
|
if isinstance(v, LocalDateTime):
|
|
dt = miutil.pydate(v)
|
|
return dt
|
|
else:
|
|
return v
|
|
|
|
def setcellvalue(self, fieldname, shapeindex, value):
|
|
"""
|
|
Set cell value in attribute table.
|
|
|
|
:param fieldname: (*string*) Field name.
|
|
:param shapeindex: (*int*) Shape index.
|
|
:param value: (*object*) Cell value to be assigned.
|
|
"""
|
|
self._layer.editCellValue(fieldname, shapeindex, value)
|
|
|
|
def setfieldvalue(self, fieldname, value, index=None):
|
|
"""
|
|
Set field value.
|
|
|
|
:param fieldname: (*str*) The field name.
|
|
:param value: (*array*) The field data array.
|
|
:param index: (*array*) Optional. Field data index. Default is `None`.
|
|
"""
|
|
value = np.asarray(value)
|
|
if index is None:
|
|
self._layer.getAttributeTable().getTable().setColumnData(fieldname, value.jarray)
|
|
else:
|
|
index = np.asarray(index)
|
|
self._layer.getAttributeTable().getTable().setColumnData(fieldname, value.jarray, index.jarray)
|
|
|
|
def shapes(self):
|
|
"""
|
|
Get shapes.
|
|
"""
|
|
return self._layer.getShapes()
|
|
|
|
def shapenum(self):
|
|
"""
|
|
Get shape number
|
|
"""
|
|
return self._layer.getShapeNum()
|
|
|
|
def legend(self):
|
|
"""
|
|
Get legend scheme.
|
|
"""
|
|
return self._layer.getLegendScheme()
|
|
|
|
def setlegend(self, legend):
|
|
"""
|
|
Set legend scheme.
|
|
|
|
:param legend: (*LegendScheme*) Legend scheme.
|
|
"""
|
|
self._layer.setLegendScheme(legend)
|
|
|
|
def update_legend(self, ltype, fieldname):
|
|
"""
|
|
Update legend scheme.
|
|
|
|
:param ltype: (*string*) Legend type [single | unique | graduate].
|
|
:param fieldname: (*string*) Field name.
|
|
"""
|
|
if ltype == 'single':
|
|
ltype = LegendType.SINGLE_SYMBOL
|
|
elif ltype == 'unique':
|
|
ltype = LegendType.UNIQUE_VALUE
|
|
elif ltyp == 'graduate':
|
|
ltype = LegendType.GRADUATED_COLOR
|
|
else:
|
|
raise ValueError(ltype)
|
|
self._layer.updateLegendScheme(ltype, fieldname)
|
|
return self._layer.getLegendScheme()
|
|
|
|
def addfield(self, fieldname, dtype, values=None):
|
|
"""
|
|
Add a field into the attribute table.
|
|
|
|
:param fieldname: (*string*) Field name.
|
|
:param dtype: (*string*) Field data type [string | int | float | double].
|
|
:param values: (*array_like*) Field values.
|
|
"""
|
|
dt = TableUtil.toDataTypes(dtype)
|
|
self._layer.editAddField(fieldname, dt)
|
|
if not values is None:
|
|
n = self.shapenum()
|
|
for i in range(n):
|
|
if i < len(values):
|
|
self._layer.editCellValue(fieldname, i, values[i])
|
|
|
|
def delfield(self, fieldname):
|
|
"""
|
|
Delete a field from the attribute table.
|
|
|
|
:param fieldname: (*string*) Filed name.
|
|
"""
|
|
self._layer.editRemoveField(fieldname)
|
|
|
|
def renamefield(self, fieldname, newfieldname):
|
|
"""
|
|
Rename the field.
|
|
|
|
:param fieldname: (*string*) The old field name.
|
|
:param newfieldname: (*string*) The new field name.
|
|
"""
|
|
self._layer.editRenameField(fieldname, newfieldname)
|
|
|
|
def addshape(self, x, y, fields=None, z=None, m=None):
|
|
"""
|
|
Add a shape.
|
|
|
|
:param x: (*array_like*) X coordinates of the shape points.
|
|
:param y: (*array_like*) Y coordinates of the shape points.
|
|
:param fields: (*array_like*) Field values of the shape.
|
|
:param z: (*array_like*) Optional, Z coordinates of the shape points.
|
|
:param m: (*array_like*) Optional, M coordinates of the shape points.
|
|
"""
|
|
shapes = geoutil.makeshapes(x, y, self.shapetype, z, m)
|
|
if len(shapes) == 1:
|
|
self._layer.editAddShape(shapes[0], fields)
|
|
else:
|
|
for shape, field in zip(shapes, fields):
|
|
self._layer.editAddShape(shape, field)
|
|
|
|
def del_shape(self, shape):
|
|
"""
|
|
Delete a shape.
|
|
|
|
:param shape: (*Shape or int*) The shape or shape index to be deleted.
|
|
"""
|
|
self._layer.editRemoveShape(shape)
|
|
|
|
def copy(self):
|
|
"""
|
|
Copy the layer.
|
|
|
|
:return: (*MILayer*) Copied layer.
|
|
"""
|
|
return MILayer(layer=self._layer.clone())
|
|
|
|
def move(self, xshift=0, yshift=0):
|
|
"""
|
|
Move shapes.
|
|
|
|
:param xshift: (*float*) X shift.
|
|
:param yshift: (*float*) Y shift.
|
|
"""
|
|
self._layer.move(xshift, yshift)
|
|
|
|
def addlabels(self, fieldname, **kwargs):
|
|
"""
|
|
Add labels
|
|
|
|
:param fieldname: (*string*) Field name
|
|
:param fontname: (*string*) Font name. Default is ``Arial``.
|
|
:param fontsize: (*string*) Font size. Default is ``14``.
|
|
:param bold: (*boolean*) Font bold or not. Default is ``False``.
|
|
:param color: (*color*) Label color. Default is ``None`` with black color.
|
|
:param xoffset: (*int*) X coordinate offset. Default is ``0``.
|
|
:param yoffset: (*int*) Y coordinate offset. Default is ``0``.
|
|
:param avoidcoll: (*boolean*) Avoid labels collision or not. Default is ``True``.
|
|
:param decimals: (*int*) Number of decimals of labels.
|
|
"""
|
|
labelset = self._layer.getLabelSet()
|
|
labelset.setFieldName(fieldname)
|
|
fontname = kwargs.pop('fontname', 'Arial')
|
|
fontsize = kwargs.pop('fontsize', 14)
|
|
bold = kwargs.pop('bold', False)
|
|
if bold:
|
|
font = Font(fontname, Font.BOLD, fontsize)
|
|
else:
|
|
font = Font(fontname, Font.PLAIN, fontsize)
|
|
labelset.setLabelFont(font)
|
|
color = kwargs.pop('color', None)
|
|
if not color is None:
|
|
color = miutil.getcolor(color)
|
|
labelset.setLabelColor(color)
|
|
xoffset = kwargs.pop('xoffset', 0)
|
|
labelset.setXOffset(xoffset)
|
|
yoffset = kwargs.pop('yoffset', 0)
|
|
labelset.setYOffset(yoffset)
|
|
avoidcoll = kwargs.pop('avoidcoll', True)
|
|
labelset.setAvoidCollision(avoidcoll)
|
|
decimals = kwargs.pop('decimals', None)
|
|
if not decimals is None:
|
|
labelset.setAutoDecimal(False)
|
|
labelset.setDecimalDigits(decimals)
|
|
self._layer.addLabels()
|
|
|
|
def getlabel(self, text):
|
|
"""
|
|
Get a label.
|
|
|
|
:param text: (*string*) The label text.
|
|
"""
|
|
return self._layer.getLabel(text)
|
|
|
|
def movelabel(self, label, x=0, y=0):
|
|
"""
|
|
Move a label.
|
|
|
|
:param label: (*string*) The label text.
|
|
:param x: (*float*) X shift for moving in pixel unit.
|
|
:param y: (*float*) Y shift for moving in pixel unit.
|
|
"""
|
|
self._layer.moveLabel(label, x, y)
|
|
|
|
def add_charts(self, fieldnames, legend=None, **kwargs):
|
|
"""
|
|
Add charts
|
|
|
|
:param fieldnames: (*list of string*) Field name list.
|
|
:param legend: (*LegendScheme*) Chart legend.
|
|
:param charttype: (*string*) Chart type [bar | pie]. Default is ``bar``.
|
|
:param minsize: (*int*) Minimum chart size. Default is ``0``.
|
|
:param maxsize: (*int*) Maximum chart size. Default is ``50``.
|
|
:param barwidth: (*int*) Bar width. Only valid for bar chart. Default is ``8``.
|
|
:param xoffset: (*int*) X coordinate offset. Default is ``0``.
|
|
:param yoffset: (*int*) Y coordinate offset. Default is ``0``.
|
|
:param avoidcoll: (*boolean*) Avoid labels collision or not. Default is ``True``.
|
|
:param align: (*string*) Chart align type [center | left | right | none], Default is ``center``.
|
|
:param view3d: (*boolean*) Draw chart as 3D or not. Default is ``False``.
|
|
:param thickness: (*int*) 3D chart thickness. Default is ``5``.
|
|
:param drawlabel: (*boolean*) Draw label or not. Default is ``False``.
|
|
:param fontname: (*string*) Label font name.
|
|
:param fontsize: (*int*) Label font size.
|
|
:param bold: (*boolean*) Font bold or not. Default is ``False``.
|
|
:param labelcolor: (*color*) Label color.
|
|
:param decimals: (*int*) Number of decimals of labels.
|
|
"""
|
|
charttype = kwargs.pop('charttype', None)
|
|
minsize = kwargs.pop('minsize', None)
|
|
maxsize = kwargs.pop('maxsize', None)
|
|
barwidth = kwargs.pop('barwidth', None)
|
|
xoffset = kwargs.pop('xoffset', None)
|
|
yoffset = kwargs.pop('yoffset', None)
|
|
avoidcoll = kwargs.pop('avoidcoll', None)
|
|
align = kwargs.pop('align', None)
|
|
view3d = kwargs.pop('view3d', None)
|
|
thickness = kwargs.pop('thickness', None)
|
|
drawlabel = kwargs.pop('drawlabel', None)
|
|
fontname = kwargs.pop('fontname', 'Arial')
|
|
fontsize = kwargs.pop('fontsize', 12)
|
|
bold = kwargs.pop('bold', False)
|
|
if bold:
|
|
font = Font(fontname, Font.BOLD, fontsize)
|
|
else:
|
|
font = Font(fontname, Font.PLAIN, fontsize)
|
|
labelcolor = kwargs.pop('labelcolor', None)
|
|
decimals = kwargs.pop('decimals', None)
|
|
|
|
chartset = self._layer.getChartSet()
|
|
chartset.setFieldNames(fieldnames)
|
|
chartset.setLegendScheme(legend)
|
|
if not charttype is None:
|
|
chartset.setChartType(charttype)
|
|
if not minsize is None:
|
|
chartset.setMinSize(minsize)
|
|
if not maxsize is None:
|
|
chartset.setMaxSize(maxsize)
|
|
if not barwidth is None:
|
|
chartset.setBarWidth(barwidth)
|
|
if not xoffset is None:
|
|
chartset.setXShift(xoffset)
|
|
if not yoffset is None:
|
|
chartset.setYShift(yoffset)
|
|
if not avoidcoll is None:
|
|
chartset.setAvoidCollision(avoidcoll)
|
|
if not align is None:
|
|
chartset.setAlignType(align)
|
|
if not view3d is None:
|
|
chartset.setView3D(view3d)
|
|
if not thickness is None:
|
|
chartset.setThickness(thickness)
|
|
if not drawlabel is None:
|
|
chartset.setDrawLabel(drawlabel)
|
|
chartset.setLabelFont(font)
|
|
if not labelcolor is None:
|
|
chartset.setLabelColor(miutil.getcolor(labelcolor))
|
|
if not decimals is None:
|
|
chartset.setDecimalDigits(decimals)
|
|
self._layer.updateChartSet()
|
|
self._layer.addCharts()
|
|
return chartset
|
|
|
|
def get_chartlegend(self):
|
|
"""
|
|
Get legend of the chart graphics.
|
|
"""
|
|
return self._layer.getChartSet().getLegendScheme()
|
|
|
|
def get_chart(self, index):
|
|
"""
|
|
Get a chart graphic.
|
|
|
|
:param index: (*int*) Chart index.
|
|
|
|
:returns: Chart graphic
|
|
"""
|
|
return self._layer.getChartPoints()[index]
|
|
|
|
def move_chart(self, index, x=0, y=0):
|
|
"""
|
|
Move a chart graphic.
|
|
|
|
:param index: (*int*) Chart index.
|
|
:param x: (*float*) X shift for moving.
|
|
:param y: (*float*) Y shift for moving.
|
|
"""
|
|
s = self._layer.getChartPoints()[index].getShape()
|
|
p = s.getPoint()
|
|
p.X = p.X + x
|
|
p.Y = p.Y + y
|
|
s.setPoint(p)
|
|
|
|
def set_avoidcoll(self, avoidcoll):
|
|
"""
|
|
Set if avoid collision or not. Only valid for VectorLayer with Point shapes.
|
|
|
|
:param avoidcoll: (*boolean*) Avoid collision or not.
|
|
"""
|
|
self._layer.setAvoidCollision(avoidcoll)
|
|
|
|
def project(self, toproj):
|
|
"""
|
|
Project to another projection.
|
|
|
|
:param toproj: (*ProjectionInfo*) The projection to be projected.
|
|
"""
|
|
GeoProjectionUtil.projectLayer(self._layer, toproj)
|
|
|
|
def buffer(self, dist=0, merge=False):
|
|
"""
|
|
Get the buffer layer.
|
|
|
|
:param dist: (*float*) Buffer value.
|
|
:param merge: (*boolean*) Merge the buffered shapes or not.
|
|
|
|
:returns: (*MILayer*) Buffered layer.
|
|
"""
|
|
r = self._layer.buffer(dist, False, merge)
|
|
return MILayer(r)
|
|
|
|
def clip(self, clipobj):
|
|
"""
|
|
Clip this layer by polygon or another polygon layer.
|
|
|
|
:param clipobj: (*PolygonShape or MILayer*) Clip object.
|
|
|
|
:returns: (*MILayer*) Clipped layer.
|
|
"""
|
|
if isinstance(clipobj, PolygonShape):
|
|
clipobj = [clipobj]
|
|
elif isinstance(clipobj, MILayer):
|
|
clipobj = clipobj._layer
|
|
r = self._layer.clip(clipobj)
|
|
return MILayer(r)
|
|
|
|
def select(self, expression, seltype='new'):
|
|
"""
|
|
Select shapes by SQL expression.
|
|
|
|
:param expression: (*string*) SQL expression.
|
|
:param seltype: (*string*) Selection type ['new' | 'add_to_current' |
|
|
'remove_from_current' | 'select_from_current']
|
|
|
|
:returns: (*list of Shape*) Selected shape list.
|
|
"""
|
|
self._layer.sqlSelect(expression, seltype)
|
|
return self._layer.getSelectedShapes()
|
|
|
|
def clear_selection(self):
|
|
"""
|
|
Clear shape selection.
|
|
"""
|
|
self._layer.clearSelectedShapes()
|
|
|
|
def clone(self):
|
|
"""
|
|
Clone self.
|
|
"""
|
|
return MILayer(self._layer.clone())
|
|
|
|
def save(self, fn=None, encoding=None):
|
|
"""
|
|
Save layer as shape file.
|
|
|
|
:param fn: (*string*) Shape file name (.shp).
|
|
:param encoding: (*string*) Encoding.
|
|
"""
|
|
if fn is None:
|
|
fn = self._layer.getFileName()
|
|
|
|
if fn.strip() == '':
|
|
print('File name is needed to save the layer!')
|
|
raise IOError
|
|
else:
|
|
if encoding is None:
|
|
self._layer.saveFile(fn)
|
|
else:
|
|
self._layer.saveFile(fn, encoding)
|
|
|
|
def save_kml(self, fn):
|
|
"""
|
|
Save layer as KML file.
|
|
|
|
:param fn: (*string*) KML file name.
|
|
"""
|
|
self._layer.saveAsKMLFile(fn)
|
|
|
|
def save_bil(self, fn, proj=None):
|
|
"""
|
|
Save layer as bil file.
|
|
|
|
:param fn: (*str*) Bil file name.
|
|
:param proj: (*ProjectionInfo*) Projection. Default is None.
|
|
"""
|
|
if proj is None:
|
|
self._layer.saveFile(fn)
|
|
else:
|
|
self._layer.saveFile(fn, proj)
|
|
|
|
def save_geojson(self, fn):
|
|
"""
|
|
Save layer as GeoJSON file.
|
|
|
|
:param fn: (*str*) GeoJSON file name.
|
|
"""
|
|
features = GeoJSONWriter.write(self._layer)
|
|
with open(fn, 'w') as f:
|
|
f.write("{\n")
|
|
f.write(""""type": "FeatureCollection",\n""")
|
|
f.write(""""features": [\n""")
|
|
for i in range(features.getNumFeatures()):
|
|
feature = features.getFeature(i)
|
|
f.write(feature.toString())
|
|
if i < features.getNumFeatures() - 1:
|
|
f.write(",\n")
|
|
else:
|
|
f.write("\n")
|
|
f.write("]\n")
|
|
f.write("}")
|
|
|
|
|
|
class MIXYListData():
|
|
def __init__(self, data=None):
|
|
if data is None:
|
|
self.data = XYListDataset()
|
|
else:
|
|
self.data = data
|
|
|
|
def __getitem__(self, indices):
|
|
if not isinstance(indices, tuple):
|
|
inds = []
|
|
inds.append(indices)
|
|
indices = inds
|
|
|
|
if isinstance(indices[0], int):
|
|
if isinstance(indices[1], int):
|
|
x = self.data.getX(indices[0], indices[1])
|
|
y = self.data.getY(indices[0], indices[1])
|
|
return x, y
|
|
else:
|
|
return self.data.getXValues(indices[0]), self.data.getXValues(indices[0])
|
|
|
|
def size(self, series=None):
|
|
if series is None:
|
|
return self.data.getSeriesCount()
|
|
else:
|
|
return self.data.getItemCount(series)
|
|
|
|
def addseries(self, xdata, ydata, key=None):
|
|
if key is None:
|
|
key = 'Series_' + str(self.size())
|
|
if isinstance(xdata, list):
|
|
self.data.addSeries(key, xdata, ydata)
|
|
else:
|
|
self.data.addSeries(key, xdata.asarray(), ydata.asarray())
|