def viewport_geo_array(map_canvas): """Obtain the map canvas current extent in EPSG:4326. :param map_canvas: A map canvas instance. :type map_canvas: QgsMapCanvas :returns: A list in the form [xmin, ymin, xmax, ymax] where all coordinates provided are in Geographic / EPSG:4326. :rtype: list .. note:: Delegates to extent_to_geo_array() """ # get the current viewport extent myRect = map_canvas.extent() if map_canvas.hasCrsTransformEnabled(): myCrs = map_canvas.mapRenderer().destinationCrs() else: # some code duplication from extentToGeoArray here # in favour of clarity of logic... myCrs = QgsCoordinateReferenceSystem() myCrs.createFromEpsg(4326) return extent_to_geo_array(myRect, myCrs)
def extentToGeoArray(theExtent, theSourceCrs): """Convert the supplied extent to geographic and return as as array. Args: theExtent: QgsRectangle to be transformed to geocrs. theSourceCrs: QgsCoordinateReferenceSystem representing the original extent's CRS. Returns: list: Transformed extents in EPSG:4326 in the form [xmin, ymin, xmix, ymax] Raises: None """ myGeoCrs = QgsCoordinateReferenceSystem() myGeoCrs.createFromEpsg(4326) myXForm = QgsCoordinateTransform( theSourceCrs, myGeoCrs) # Get the clip area in the layer's crs myTransformedExtent = myXForm.transformBoundingBox(theExtent) myGeoExtent = [myTransformedExtent.xMinimum(), myTransformedExtent.yMinimum(), myTransformedExtent.xMaximum(), myTransformedExtent.yMaximum()] return myGeoExtent
def _extentsToLayer(self): """Memory layer for aggregation by using canvas extents as feature. We do this because the user elected to use no aggregation layer so we make a 'dummy' one which covers the whole study area extent. This layer is needed when postprocessing because we always want a vector layer to store aggregation information in. Returns: QgsMapLayer - a memory layer representing the extents of the clip. """ # Note: this code duplicates from Dock.viewportGeoArray - make DRY. TS myRect = self.iface.mapCanvas().extent() myCrs = QgsCoordinateReferenceSystem() myCrs.createFromEpsg(4326) myGeoExtent = extent_to_geo_array(myRect, myCrs) if not self.layer.isValid(): myMessage = self.tr( 'An exception occurred when creating the entire area layer.') raise (Exception(myMessage)) myProvider = self.layer.dataProvider() myAttrName = self.tr('Area') myProvider.addAttributes( [QgsField(myAttrName, QtCore.QVariant.String)]) self.layer.startEditing() # add a feature the size of the impact layer bounding box myFeature = QgsFeature() # noinspection PyCallByClass,PyTypeChecker,PyArgumentList myFeature.setGeometry(QgsGeometry.fromRect( QgsRectangle( QgsPoint(myGeoExtent[0], myGeoExtent[1]), QgsPoint(myGeoExtent[2], myGeoExtent[3])))) myFeature.setAttributeMap({0: QtCore.QVariant( self.tr('Entire area'))}) myProvider.addFeatures([myFeature]) self.layer.commitChanges() try: self.keywordIO.update_keywords( self.layer, {self.defaults['AGGR_ATTR_KEY']: myAttrName}) except InvalidParameterError: self.keywordIO.write_keywords( self.layer, {self.defaults['AGGR_ATTR_KEY']: myAttrName}) except KeywordDbError, e: raise e
def zoomTo(self, name): for res in self.results: if unicode(res.description) == unicode(name): dest_crs = self.canvas.mapRenderer().destinationCrs() src_crs = QgsCoordinateReferenceSystem() src_crs.createFromEpsg(res.epsg) transform = QgsCoordinateTransform(src_crs, dest_crs) new_point = transform.transform(res.x, res.y) x = new_point.x() y = new_point.y() self.canvas.setExtent(QgsRectangle(x,y,x,y)) self.canvas.zoomScale(res.zoom) self.canvas.refresh() self.marker.setCenter(new_point) self.marker.show() return
def zoomTo(self, name): for res in self.results: if unicode(res.description) == unicode(name): dest_crs = self.canvas.mapRenderer().destinationCrs() src_crs = QgsCoordinateReferenceSystem() src_crs.createFromEpsg(res.epsg) transform = QgsCoordinateTransform(src_crs, dest_crs) new_point = transform.transform(res.x, res.y) x = new_point.x() y = new_point.y() self.canvas.setExtent(QgsRectangle(x, y, x, y)) self.canvas.zoomScale(res.zoom) self.canvas.refresh() self.marker.setCenter(new_point) self.marker.show() return
def setCanvasCrs(theEpsgId, theOtfpFlag=False): """Helper to set the crs for the CANVAS before a test is run. Args: * theEpsgId - Valid EPSG identifier (int) * theOtfpFlag - whether on the fly projections should be enabled on the CANVAS. Default to False. """ # Enable on-the-fly reprojection CANVAS.mapRenderer().setProjectionsEnabled(theOtfpFlag) # Create CRS Instance myCrs = QgsCoordinateReferenceSystem() myCrs.createFromEpsg(theEpsgId) # google mercator # Reproject all layers to WGS84 geographic CRS CANVAS.mapRenderer().setDestinationCrs(myCrs)
def visible(self,value): if value == True: if self.x is not None and self.y is not None: self._visible = True dest_crs = self.canvas.mapRenderer().destinationCrs() src_crs = QgsCoordinateReferenceSystem() src_crs.createFromEpsg(self.epsg) transform = QgsCoordinateTransform(src_crs, dest_crs) new_point = transform.transform(self.x, self.y) self._xtrans = new_point.x() self._ytrans = new_point.y() self.marker.setCenter(new_point) self.marker.show() else: self._visible = False raise ValueError("Can't show marker without x and y coordinates.") else: self._visible = False self.marker.hide()
def getWGS84resolution(theLayer, theGeoExtent=None): """Return resolution of raster layer in EPSG:4326 Input theLayer: Raster layer theGeoExtent: Bounding box in EPSG:4326 # FIXME (Ole), the second argumunt should be obtained within this function to make it independent Output resolution. If input layer is already in EPSG:4326, simply return the resolution If not, work it out based on EPSG:4326 representations of its extent """ msg = tr('Input layer to getWGS84resolution must be a raster layer. ' 'I got: %s' % str(theLayer.type())[1:-1]) if not theLayer.type() == QgsMapLayer.RasterLayer: raise RuntimeError(msg) if theLayer.crs().authid() == 'EPSG:4326': # If it is already in EPSG:4326, simply use the native resolution myCellSize = theLayer.rasterUnitsPerPixel() else: # Otherwise, work it out based on EPSG:4326 representations of # its extent # Reproject extent to EPSG:4326 myGeoCrs = QgsCoordinateReferenceSystem() myGeoCrs.createFromEpsg(4326) # Estimate cellsize # FIXME (Ole): Get geoextent from layer myColumns = theLayer.width() myGeoWidth = abs(theGeoExtent[3] - theGeoExtent[0]) myCellSize = myGeoWidth / myColumns return myCellSize
def _createPolygonLayer(self, crs=None, fields=None): """Creates an empty shape file layer""" if crs is None: crs = QgsCoordinateReferenceSystem() crs.createFromEpsg(4326) if fields is None: fields = {} myTempdir = temp_dir(sub_dir='preprocess') myOutFilename = unique_filename(suffix='.shp', dir=myTempdir) mySHPWriter = QgsVectorFileWriter(myOutFilename, 'UTF-8', fields, QGis.WKBPolygon, crs) #flush the writer to write to file del mySHPWriter myName = self.tr('Entire area') myLayer = QgsVectorLayer(myOutFilename, myName, 'ogr') LOGGER.debug('created' + myLayer.name()) return myLayer
def coordRefSys(self, mapCoordSys): epsg = self.epsgList[0] coordRefSys = QgsCoordinateReferenceSystem() if QGis.QGIS_VERSION_INT >= 10900: idEpsgRSGoogle = "EPSG:%d" % epsg createCrs = coordRefSys.createFromOgcWmsCrs(idEpsgRSGoogle) else: idEpsgRSGoogle = epsg createCrs = coordRefSys.createFromEpsg(idEpsgRSGoogle) if not createCrs: google_proj_def = "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 " google_proj_def += "+units=m +nadgrids=@null +wktext +no_defs" isOk = coordRefSys.createFromProj4(google_proj_def) if not isOk: return None return coordRefSys
def coordRefSys(self, mapCoordSys): epsg = self.epsgList[0] coordRefSys = QgsCoordinateReferenceSystem() if QGis.QGIS_VERSION_INT >= 10900: idEpsgRSGoogle = "EPSG:%d" % epsg createCrs = coordRefSys.createFromOgcWmsCrs(idEpsgRSGoogle) else: idEpsgRSGoogle = epsg createCrs = coordRefSys.createFromEpsg(idEpsgRSGoogle) if not createCrs: proj_def = "+proj=tmerc +lat_0=38 +lon_0=127.5 +k=0.9996 +x_0=1000000 +y_0=2000000 +ellps=GRS80 " proj_def += "+towgs84=0,0,0,0,0,0,0 +units=m +no_defs" isOk = coordRefSys.createFromProj4(proj_def) if not isOk: return None return coordRefSys
class ToGrid(Operator): [STAT_AREA_IDX, STAT_COUNT_IDX]= range(2) def __init__(self, options=None, name='Base Abstract Grid Operator'): super(ToGrid, self).__init__(options, name) self._tmp_dir = options['tmp_dir'] self.mercator_crs = QgsCoordinateReferenceSystem() self.mercator_crs.createFromEpsg(3395) @property def output_types(self): return [OperatorDataTypes.Grid, OperatorDataTypes.Shapefile] @property def output_names(self): return ["Grid with Zone and Count Attributes", "Grid Shapefile"] output_descriptions = output_names # protected methods override ########################### def _verify_inputs(self, inputs): pass def _verify_outputs(self, outputs): pass # protected methods ########################### def _outputGeometryType(self): return QGis.WKBPolygon def _outputGeometryFromLatLon(self, lat, lon): # conversion lat/lon to grid_id is required # it acts as rounding function to lat/lon return self._outputGeometryFromGridId(latlon_to_grid(lat, lon)) def _outputGeometryFromGridId(self, grid_id): [lat, lon] = grid_to_latlon(int(grid_id)) return QgsGeometry.fromRect(QgsRectangle(lon-DEFAULT_HALF_GRID_SIZE, lat-DEFAULT_HALF_GRID_SIZE, lon+DEFAULT_HALF_GRID_SIZE, lat+DEFAULT_HALF_GRID_SIZE)) def _create_grid(self, grid_name, grid_file, x_min, y_min, x_max, y_max, x_off, y_off): x_off2, y_off2 = x_off / 2.0, y_off / 2.0 x_min = floor(x_min / x_off) * x_off x_max = ceil(x_max / x_off) * x_off y_min = floor(y_min / y_off) * y_off y_max = ceil(y_max / y_off) * y_off xtotal = int((x_max - x_min) / x_off)+1 ytotal = int((y_max - y_min) / y_off)+1 logAPICall.log('x_min %f x_max %f y_min %f y_max %f x_off %f y_off %f xtotal %d, ytotal %d' % (x_min, x_max, y_min, y_max, x_off, y_off, xtotal, ytotal), logAPICall.DEBUG_L2) fields = { 0 : QgsField('GRID_GID', QVariant.String), } writer = QgsVectorFileWriter(grid_file, "utf-8", fields, QGis.WKBPolygon, self._crs, "ESRI Shapefile") f = QgsFeature() for x in range(xtotal): for y in range(ytotal): lon = x_min + (x * x_off) + (x_off2) lat = y_min + (y * y_off) + (y_off2) #out_geom = QgsGeometry.fromRect(QgsRectangle(lon-x_off2, lat-y_off2, # lon+x_off2, lat+y_off2)) f.setGeometry(self._outputGeometryFromLatLon(lat, lon)) f.addAttribute(0, QVariant(latlon_to_grid(lat, lon))) writer.addFeature(f) del writer return load_shapefile(grid_file, grid_name) def _create_zone_statistics(self, zone_layer, zone_field, count_field, zone_stat, zone_names): # project geometry into mercator and get area in m2 mercator_transform = QgsCoordinateTransform(zone_layer.crs(), self.mercator_crs) zone_gid_idx = layer_field_index(zone_layer, GID_FIELD_NAME) zone_field_idx = layer_field_index(zone_layer, zone_field) count_field_idx = layer_field_index(zone_layer, count_field) for _f in layer_features(zone_layer): # project into mercator and get area in m2 geom = _f.geometry() geom.transform(mercator_transform) area = geom.area() gid = _f.attributeMap()[zone_gid_idx].toString() # if count field is not defined, then set count to 0 if count_field_idx >= 0: count = _f.attributeMap()[count_field_idx].toDouble()[0] else: count = 0 self._update_stat(zone_stat, gid, count, area) zone_names[gid] = _f.attributeMap()[zone_field_idx] def _update_stat(self, stats, key, count, area): if not stats.has_key(key): stats[key] = [0,0] stat = stats[key] stat[ToGrid.STAT_COUNT_IDX] +=count stat[ToGrid.STAT_AREA_IDX] +=area def _load_output(self, output_file, output_layername): output_layer = load_shapefile(output_file, output_layername) if not output_layer: raise OperatorError('Error loading grid file' % (output_file), self.__class__) self.outputs[0].value = output_layer self.outputs[1].value = output_file
def do_operation(self): """ perform create mapping scheme operation """ # validate inputs fp_layer = self.inputs[0].value zone_layer = self.inputs[1].value zone_field = self.inputs[2].value count_field = self.inputs[3].value area_field = self.inputs[4].value # make sure input is correct # NOTE: these checks cannot be performed at set input time # because the data layer maybe is not loaded yet self._test_layer_loaded(fp_layer) self._test_layer_loaded(zone_layer) self._test_layer_field_exists(zone_layer, GID_FIELD_NAME) self._test_layer_field_exists(zone_layer, zone_field) # count_field is not required # if count field is not defined, then generate building count from footprints # area_field is not required # local variables analyzer = QgsOverlayAnalyzer() area_idx = ToGrid.STAT_AREA_IDX cnt_idx = ToGrid.STAT_COUNT_IDX zone_names, zone_stat, zone_stat2, zone_totals = {}, {}, {}, {} # 1. find building count and total area for each zone # project geometry into mercator and get area in m2 mercator_crs = QgsCoordinateReferenceSystem() mercator_crs.createFromEpsg(3395) mercator_transform = QgsCoordinateTransform(zone_layer.crs(), mercator_crs) try: # use zone geometry area self._create_zone_statistics(zone_layer, zone_field, count_field, zone_stat, zone_names) except Exception as err: raise OperatorError(str(err), self.__class__) # 2. create grids around extent of zone tmp_grid1 = 'grid_' + get_unique_filename() tmp_grid1_file = self._tmp_dir + tmp_grid1 + '.shp' extent = zone_layer.extent() [x_min, y_min, x_max, y_max] = [extent.xMinimum(), extent.yMinimum(), extent.xMaximum(), extent.yMaximum()] tmp_grid_lyr1 = self._create_grid(tmp_grid1, tmp_grid1_file, \ x_min, y_min, x_max, y_max, \ DEFAULT_GRID_SIZE, DEFAULT_GRID_SIZE) # tally total building area if there is defined bldg_area_idx = layer_field_index(zone_layer, area_field) zone_area = {} zone_has_area = False if bldg_area_idx > 0: zone_has_area = True zone_gid_idx = layer_field_index(zone_layer, GID_FIELD_NAME) for _f in layer_features(zone_layer): gid = _f.attributeMap()[zone_gid_idx].toString() area = _f.attributeMap()[bldg_area_idx].toDouble()[0] if zone_area.has_key(gid): zone_area[gid] = str(float(zone_area[gid]))+area else: zone_area[gid] = area # 3. intersect grids and zones to obtain polygons with # - grid_id and zone_id # - ratio of grid covered by zone (polygon area / zone area) # apply ratio to zone building count to obtain count assigned to polygon tmp_join = 'joined_%s' % get_unique_filename() tmp_join_file = '%s%s.shp' % (self._tmp_dir, tmp_join) try: # do intersection analyzer.intersection(tmp_grid_lyr1, zone_layer, tmp_join_file) tmp_join_layer = load_shapefile(tmp_join_file, tmp_join) except AssertionError as err: raise OperatorError(str(err), self.__class__) except Exception as err: raise OperatorError(str(err), self.__class__) # do tally zone_gid_idx = layer_field_index(tmp_join_layer, GID_FIELD_NAME) grid_gid_idx = layer_field_index(tmp_join_layer, "GRID_GID") bldg_cnt_idx = layer_field_index(tmp_join_layer, count_field) for _f in layer_features(tmp_join_layer): geom = _f.geometry() geom.transform(mercator_transform) area = geom.area() # generate all stats of interest zone_gid = _f.attributeMap()[zone_gid_idx].toString() grid_gid = _f.attributeMap()[grid_gid_idx].toString() stat = zone_stat[zone_gid] # calculate count/area as proportion of total zone area area_ratio = (area/stat[area_idx]) if bldg_cnt_idx > 0: bldg_cnt = _f.attributeMap()[bldg_cnt_idx].toDouble()[0] * area_ratio else: bldg_cnt = 0 if zone_has_area: area = zone_area[zone_gid] * area_ratio else: area = stat[area_idx] * area_ratio self._update_stat(zone_stat2, '%s|%s'%(grid_gid, zone_gid), bldg_cnt, area) # 4. find total buildings in each zone based on footprint # - simply join the files and tally count and total area tmp_join1 = 'joined_%s' % get_unique_filename() tmp_join1_file = '%s%s.shp' % (self._tmp_dir, tmp_join1) try: # do intersection analyzer.intersection(fp_layer, tmp_join_layer, tmp_join1_file) tmp_join1_layer = load_shapefile(tmp_join1_file, tmp_join1) except AssertionError as err: raise OperatorError(str(err), self.__class__) except Exception as err: raise OperatorError(str(err), self.__class__) # do tally zone_fp_stat = {} zone_gid_idx = layer_field_index(tmp_join1_layer, '%s_'% GID_FIELD_NAME) grid_gid_idx = layer_field_index(tmp_join1_layer, "GRID_GID") fp_area_idx = layer_field_index(tmp_join1_layer, AREA_FIELD_NAME) fp_ht_idx = layer_field_index(tmp_join1_layer, HT_FIELD_NAME) fp_has_height = False for _f in layer_features(tmp_join1_layer): zone_gid = _f.attributeMap()[zone_gid_idx].toString() grid_gid = _f.attributeMap()[grid_gid_idx].toString() area = _f.attributeMap()[fp_area_idx].toDouble()[0] # area comes from geometry, always exists ht = _f.attributeMap()[fp_ht_idx].toDouble()[0] if ht > 0: fp_has_height = True area *= ht # this is actual area to be aggregated at the end self._update_stat(zone_fp_stat, '%s|%s'%(grid_gid, zone_gid), 1, area) self._update_stat(zone_totals, zone_gid, 1, area) # 5. generate grid with adjusted building counts fields = { 0 : QgsField(GID_FIELD_NAME, QVariant.String), 1 : QgsField(zone_field, QVariant.String), 2 : QgsField(CNT_FIELD_NAME, QVariant.Double), 3 : QgsField(AREA_FIELD_NAME, QVariant.Double), } output_layername = 'grid_%s' % get_unique_filename() output_file = '%s%s.shp' % (self._tmp_dir, output_layername) writer = QgsVectorFileWriter(output_file, "utf-8", fields, QGis.WKBPolygon, self._crs, "ESRI Shapefile") f = QgsFeature() for key in zone_stat2.keys(): (grid_gid, zone_gid) = str(key).split("|") s_zone = zone_stat[QString(zone_gid)] # overall statistics for the zone from zone file (always exists) s_zone_grid = zone_stat2[key] # grid specific statistic from from zone file (always exists) if zone_totals.has_key(QString(zone_gid)): # overall statistics for the zone from footprints s_total = zone_totals[QString(zone_gid)] else: s_total = [0,0] # set to zero if missing if zone_fp_stat.has_key(key): # grid specific statistic from from footprint s_fp = zone_fp_stat[key] else: s_fp = [0, 0] # set to zero if missing zone_leftover_count = s_zone[cnt_idx] - s_total[cnt_idx] if zone_has_area: zone_leftover_area = zone_area[QString(zone_gid)] - s_total[area_idx] else: zone_leftover_area = s_zone[area_idx] - s_total[area_idx] if zone_leftover_count > 0: # there are still building not accounted for # distribute to grid based on ratio of grid leftover area over zone leftover area # (leftover area is area of zone after subtracting footprint areas grid_leftover_count = zone_leftover_count * ((s_zone_grid[area_idx]-s_fp[area_idx])/zone_leftover_area) grid_count = s_fp[cnt_idx] + grid_leftover_count else: grid_count = s_fp[cnt_idx] if fp_has_height: # area can be actual area based on footprint area * height area = s_fp[area_idx] elif zone_has_area: area = s_zone_grid[area_idx] else: # no area defined area = 0 # max(s_zone_grid[area_idx], s_fp[area_idx]) f.setGeometry(self._outputGeometryFromGridId(grid_gid)) f.addAttribute(0, grid_gid) f.addAttribute(1, zone_names[QString(zone_gid)]) f.addAttribute(2, grid_count) f.addAttribute(3, area) writer.addFeature(f) del writer # clean up del tmp_grid_lyr1 del tmp_join_layer del tmp_join1_layer remove_shapefile(tmp_grid1_file) remove_shapefile(tmp_join_file) remove_shapefile(tmp_join1_file) # store data in output self._load_output(output_file, output_layername)
def do_operation(self): """ perform footprint load operation """ # input/output data checking already done during property set # load and verify infile = self.inputs[0].value tmp_fp_layername = 'fp_%s' % get_unique_filename() tmp_fp_layer = load_shapefile(infile, tmp_fp_layername) if not tmp_fp_layer: raise OperatorError('Error loading footprint file' % (infile), self.__class__) if self._fp_ht_field is not None: ht_idx = layer_field_index(tmp_fp_layer, self._fp_ht_field) else: ht_idx = -1 logAPICall.log( 'tmp_fp_layer.crs().epsg() %s ' % tmp_fp_layer.crs().epsg(), logAPICall.DEBUG) if tmp_fp_layer.crs().epsg() != self._crs.epsg(): transform = QgsCoordinateTransform(tmp_fp_layer.crs(), self._crs) transform_required = True else: transform_required = False mercator_crs = QgsCoordinateReferenceSystem() #mercator_crs.createFromProj4("+proj=merc +lon_0=0 +k=1 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs") mercator_crs.createFromEpsg(3395) mercator_transform = QgsCoordinateTransform(tmp_fp_layer.crs(), mercator_crs) # output grid fields = { 0: QgsField(GID_FIELD_NAME, QVariant.Int), 1: QgsField(LON_FIELD_NAME, QVariant.Double), 2: QgsField(LAT_FIELD_NAME, QVariant.Double), 3: QgsField(AREA_FIELD_NAME, QVariant.Double), 4: QgsField(HT_FIELD_NAME, QVariant.Int), } output_file = '%sfpc_%s.shp' % (self._tmp_dir, get_unique_filename()) logAPICall.log('create outputfile %s ... ' % output_file, logAPICall.DEBUG) try: writer = QgsVectorFileWriter(output_file, "utf-8", fields, QGis.WKBPoint, self._crs, "ESRI Shapefile") f = QgsFeature() gid = 0 for _f in layer_features(tmp_fp_layer): # NOTE: geom.transform does projection in place to underlying # C object, for some reason, multiple projection does not # work correctly. following is a work-around # 1. get geometry geom = _f.geometry() # 2. get original centroid point and project is required centroid = geom.centroid().asPoint() if transform_required: t_centroid = transform.transform(centroid) else: t_centroid = centroid # 3. project into mercator and get area in m2 geom.transform(mercator_transform) area = geom.area() # write to file gid += 1 f.setGeometry(QgsGeometry.fromPoint(t_centroid)) f.addAttribute(0, QVariant(gid)) f.addAttribute(1, QVariant(t_centroid.x())) f.addAttribute(2, QVariant(t_centroid.y())) f.addAttribute(3, QVariant(area)) if ht_idx != -1: f.addAttribute(4, _f.attributeMap()[ht_idx]) else: f.addAttribute(4, QVariant(0)) writer.addFeature(f) del writer, f except Exception as err: remove_shapefile(output_file) raise OperatorError("error creating footprint centroids: %s" % err, self.__class__) fp_layer = load_shapefile(output_file, tmp_fp_layername) if not fp_layer: raise OperatorError( 'Error loading footprint centroid file' % (output_file), self.__class__) # clean up del tmp_fp_layer # store data in output self.outputs[0].value = fp_layer self.outputs[1].value = output_file
class ToGrid(Operator): [STAT_AREA_IDX, STAT_COUNT_IDX] = range(2) def __init__(self, options=None, name="Base Abstract Grid Operator"): super(ToGrid, self).__init__(options, name) self._tmp_dir = options["tmp_dir"] self.mercator_crs = QgsCoordinateReferenceSystem() self.mercator_crs.createFromEpsg(3395) @property def output_types(self): return [OperatorDataTypes.Grid, OperatorDataTypes.Shapefile] @property def output_names(self): return ["Grid with Zone and Count Attributes", "Grid Shapefile"] output_descriptions = output_names # protected methods override ########################### def _verify_inputs(self, inputs): pass def _verify_outputs(self, outputs): pass # protected methods ########################### def _outputGeometryType(self): return QGis.WKBPolygon def _outputGeometryFromLatLon(self, lat, lon): # conversion lat/lon to grid_id is required # it acts as rounding function to lat/lon return self._outputGeometryFromGridId(latlon_to_grid(lat, lon)) def _outputGeometryFromGridId(self, grid_id): [lat, lon] = grid_to_latlon(int(grid_id)) return QgsGeometry.fromRect( QgsRectangle( lon - DEFAULT_HALF_GRID_SIZE, lat - DEFAULT_HALF_GRID_SIZE, lon + DEFAULT_HALF_GRID_SIZE, lat + DEFAULT_HALF_GRID_SIZE, ) ) def _create_grid(self, grid_name, grid_file, x_min, y_min, x_max, y_max, x_off, y_off): x_off2, y_off2 = x_off / 2.0, y_off / 2.0 x_min = floor(x_min / x_off) * x_off x_max = ceil(x_max / x_off) * x_off y_min = floor(y_min / y_off) * y_off y_max = ceil(y_max / y_off) * y_off xtotal = int((x_max - x_min) / x_off) + 1 ytotal = int((y_max - y_min) / y_off) + 1 logAPICall.log( "x_min %f x_max %f y_min %f y_max %f x_off %f y_off %f xtotal %d, ytotal %d" % (x_min, x_max, y_min, y_max, x_off, y_off, xtotal, ytotal), logAPICall.DEBUG_L2, ) fields = {0: QgsField("GRID_GID", QVariant.String)} writer = QgsVectorFileWriter(grid_file, "utf-8", fields, QGis.WKBPolygon, self._crs, "ESRI Shapefile") f = QgsFeature() for x in range(xtotal): for y in range(ytotal): lon = x_min + (x * x_off) + (x_off2) lat = y_min + (y * y_off) + (y_off2) # out_geom = QgsGeometry.fromRect(QgsRectangle(lon-x_off2, lat-y_off2, # lon+x_off2, lat+y_off2)) f.setGeometry(self._outputGeometryFromLatLon(lat, lon)) f.addAttribute(0, QVariant(latlon_to_grid(lat, lon))) writer.addFeature(f) del writer return load_shapefile(grid_file, grid_name) def _create_zone_statistics(self, zone_layer, zone_field, count_field, zone_stat, zone_names): # project geometry into mercator and get area in m2 mercator_transform = QgsCoordinateTransform(zone_layer.crs(), self.mercator_crs) zone_gid_idx = layer_field_index(zone_layer, GID_FIELD_NAME) zone_field_idx = layer_field_index(zone_layer, zone_field) count_field_idx = layer_field_index(zone_layer, count_field) for _f in layer_features(zone_layer): # project into mercator and get area in m2 geom = _f.geometry() geom.transform(mercator_transform) area = geom.area() gid = _f.attributeMap()[zone_gid_idx].toString() # if count field is not defined, then set count to 0 if count_field_idx >= 0: count = _f.attributeMap()[count_field_idx].toDouble()[0] else: count = 0 self._update_stat(zone_stat, gid, count, area) zone_names[gid] = _f.attributeMap()[zone_field_idx] def _update_stat(self, stats, key, count, area): if not stats.has_key(key): stats[key] = [0, 0] stat = stats[key] stat[ToGrid.STAT_COUNT_IDX] += count stat[ToGrid.STAT_AREA_IDX] += area def _load_output(self, output_file, output_layername): output_layer = load_shapefile(output_file, output_layername) if not output_layer: raise OperatorError("Error loading grid file" % (output_file), self.__class__) self.outputs[0].value = output_layer self.outputs[1].value = output_file
def do_operation(self): """ perform create mapping scheme operation """ # validate inputs fp_layer = self.inputs[0].value zone_layer = self.inputs[1].value zone_field = self.inputs[2].value count_field = self.inputs[3].value area_field = self.inputs[4].value # make sure input is correct # NOTE: these checks cannot be performed at set input time # because the data layer maybe is not loaded yet self._test_layer_loaded(fp_layer) self._test_layer_loaded(zone_layer) self._test_layer_field_exists(zone_layer, GID_FIELD_NAME) self._test_layer_field_exists(zone_layer, zone_field) # count_field is not required # if count field is not defined, then generate building count from footprints # area_field is not required # local variables analyzer = QgsOverlayAnalyzer() area_idx = ToGrid.STAT_AREA_IDX cnt_idx = ToGrid.STAT_COUNT_IDX zone_names, zone_stat, zone_stat2, zone_totals = {}, {}, {}, {} # 1. find building count and total area for each zone # project geometry into mercator and get area in m2 mercator_crs = QgsCoordinateReferenceSystem() mercator_crs.createFromEpsg(3395) mercator_transform = QgsCoordinateTransform(zone_layer.crs(), mercator_crs) try: # use zone geometry area self._create_zone_statistics(zone_layer, zone_field, count_field, zone_stat, zone_names) except Exception as err: raise OperatorError(str(err), self.__class__) # 2. create grids around extent of zone tmp_grid1 = "grid_" + get_unique_filename() tmp_grid1_file = self._tmp_dir + tmp_grid1 + ".shp" extent = zone_layer.extent() [x_min, y_min, x_max, y_max] = [extent.xMinimum(), extent.yMinimum(), extent.xMaximum(), extent.yMaximum()] tmp_grid_lyr1 = self._create_grid( tmp_grid1, tmp_grid1_file, x_min, y_min, x_max, y_max, DEFAULT_GRID_SIZE, DEFAULT_GRID_SIZE ) # tally total building area if there is defined bldg_area_idx = layer_field_index(zone_layer, area_field) zone_area = {} zone_has_area = False if bldg_area_idx > 0: zone_has_area = True zone_gid_idx = layer_field_index(zone_layer, GID_FIELD_NAME) for _f in layer_features(zone_layer): gid = _f.attributeMap()[zone_gid_idx].toString() area = _f.attributeMap()[bldg_area_idx].toDouble()[0] if zone_area.has_key(gid): zone_area[gid] = str(float(zone_area[gid])) + area else: zone_area[gid] = area # 3. intersect grids and zones to obtain polygons with # - grid_id and zone_id # - ratio of grid covered by zone (polygon area / zone area) # apply ratio to zone building count to obtain count assigned to polygon tmp_join = "joined_%s" % get_unique_filename() tmp_join_file = "%s%s.shp" % (self._tmp_dir, tmp_join) try: # do intersection analyzer.intersection(tmp_grid_lyr1, zone_layer, tmp_join_file) tmp_join_layer = load_shapefile(tmp_join_file, tmp_join) except AssertionError as err: raise OperatorError(str(err), self.__class__) except Exception as err: raise OperatorError(str(err), self.__class__) # do tally zone_gid_idx = layer_field_index(tmp_join_layer, GID_FIELD_NAME) grid_gid_idx = layer_field_index(tmp_join_layer, "GRID_GID") bldg_cnt_idx = layer_field_index(tmp_join_layer, count_field) for _f in layer_features(tmp_join_layer): geom = _f.geometry() geom.transform(mercator_transform) area = geom.area() # generate all stats of interest zone_gid = _f.attributeMap()[zone_gid_idx].toString() grid_gid = _f.attributeMap()[grid_gid_idx].toString() stat = zone_stat[zone_gid] # calculate count/area as proportion of total zone area area_ratio = area / stat[area_idx] if bldg_cnt_idx > 0: bldg_cnt = _f.attributeMap()[bldg_cnt_idx].toDouble()[0] * area_ratio else: bldg_cnt = 0 if zone_has_area: area = zone_area[zone_gid] * area_ratio else: area = stat[area_idx] * area_ratio self._update_stat(zone_stat2, "%s|%s" % (grid_gid, zone_gid), bldg_cnt, area) # 4. find total buildings in each zone based on footprint # - simply join the files and tally count and total area tmp_join1 = "joined_%s" % get_unique_filename() tmp_join1_file = "%s%s.shp" % (self._tmp_dir, tmp_join1) try: # do intersection analyzer.intersection(fp_layer, tmp_join_layer, tmp_join1_file) tmp_join1_layer = load_shapefile(tmp_join1_file, tmp_join1) except AssertionError as err: raise OperatorError(str(err), self.__class__) except Exception as err: raise OperatorError(str(err), self.__class__) # do tally zone_fp_stat = {} zone_gid_idx = layer_field_index(tmp_join1_layer, "%s_" % GID_FIELD_NAME) grid_gid_idx = layer_field_index(tmp_join1_layer, "GRID_GID") fp_area_idx = layer_field_index(tmp_join1_layer, AREA_FIELD_NAME) fp_ht_idx = layer_field_index(tmp_join1_layer, HT_FIELD_NAME) fp_has_height = False for _f in layer_features(tmp_join1_layer): zone_gid = _f.attributeMap()[zone_gid_idx].toString() grid_gid = _f.attributeMap()[grid_gid_idx].toString() area = _f.attributeMap()[fp_area_idx].toDouble()[0] # area comes from geometry, always exists ht = _f.attributeMap()[fp_ht_idx].toDouble()[0] if ht > 0: fp_has_height = True area *= ht # this is actual area to be aggregated at the end self._update_stat(zone_fp_stat, "%s|%s" % (grid_gid, zone_gid), 1, area) self._update_stat(zone_totals, zone_gid, 1, area) # 5. generate grid with adjusted building counts fields = { 0: QgsField(GID_FIELD_NAME, QVariant.String), 1: QgsField(zone_field, QVariant.String), 2: QgsField(CNT_FIELD_NAME, QVariant.Double), 3: QgsField(AREA_FIELD_NAME, QVariant.Double), } output_layername = "grid_%s" % get_unique_filename() output_file = "%s%s.shp" % (self._tmp_dir, output_layername) writer = QgsVectorFileWriter(output_file, "utf-8", fields, QGis.WKBPolygon, self._crs, "ESRI Shapefile") f = QgsFeature() for key in zone_stat2.keys(): (grid_gid, zone_gid) = str(key).split("|") s_zone = zone_stat[QString(zone_gid)] # overall statistics for the zone from zone file (always exists) s_zone_grid = zone_stat2[key] # grid specific statistic from from zone file (always exists) if zone_totals.has_key(QString(zone_gid)): # overall statistics for the zone from footprints s_total = zone_totals[QString(zone_gid)] else: s_total = [0, 0] # set to zero if missing if zone_fp_stat.has_key(key): # grid specific statistic from from footprint s_fp = zone_fp_stat[key] else: s_fp = [0, 0] # set to zero if missing zone_leftover_count = s_zone[cnt_idx] - s_total[cnt_idx] if zone_has_area: zone_leftover_area = zone_area[QString(zone_gid)] - s_total[area_idx] else: zone_leftover_area = s_zone[area_idx] - s_total[area_idx] if zone_leftover_count > 0: # there are still building not accounted for # distribute to grid based on ratio of grid leftover area over zone leftover area # (leftover area is area of zone after subtracting footprint areas grid_leftover_count = zone_leftover_count * ( (s_zone_grid[area_idx] - s_fp[area_idx]) / zone_leftover_area ) grid_count = s_fp[cnt_idx] + grid_leftover_count else: grid_count = s_fp[cnt_idx] if fp_has_height: # area can be actual area based on footprint area * height area = s_fp[area_idx] elif zone_has_area: area = s_zone_grid[area_idx] else: # no area defined area = 0 # max(s_zone_grid[area_idx], s_fp[area_idx]) f.setGeometry(self._outputGeometryFromGridId(grid_gid)) f.addAttribute(0, grid_gid) f.addAttribute(1, zone_names[QString(zone_gid)]) f.addAttribute(2, grid_count) f.addAttribute(3, area) writer.addFeature(f) del writer # clean up del tmp_grid_lyr1 del tmp_join_layer del tmp_join1_layer remove_shapefile(tmp_grid1_file) remove_shapefile(tmp_join_file) remove_shapefile(tmp_join1_file) # store data in output self._load_output(output_file, output_layername)
def do_operation(self): """ perform footprint load operation """ # input/output data checking already done during property set # load and verify infile = self.inputs[0].value tmp_fp_layername = 'fp_%s' % get_unique_filename() tmp_fp_layer = load_shapefile(infile, tmp_fp_layername) if not tmp_fp_layer: raise OperatorError('Error loading footprint file' % (infile), self.__class__) if self._fp_ht_field is not None: ht_idx = layer_field_index(tmp_fp_layer, self._fp_ht_field) else: ht_idx = -1 logAPICall.log('tmp_fp_layer.crs().epsg() %s ' % tmp_fp_layer.crs().epsg(), logAPICall.DEBUG) if tmp_fp_layer.crs().epsg() != self._crs.epsg(): transform = QgsCoordinateTransform(tmp_fp_layer.crs(), self._crs) transform_required = True else: transform_required = False mercator_crs = QgsCoordinateReferenceSystem() #mercator_crs.createFromProj4("+proj=merc +lon_0=0 +k=1 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs") mercator_crs.createFromEpsg(3395) mercator_transform = QgsCoordinateTransform(tmp_fp_layer.crs(), mercator_crs) # output grid fields = { 0 : QgsField(GID_FIELD_NAME, QVariant.Int), 1 : QgsField(LON_FIELD_NAME, QVariant.Double), 2 : QgsField(LAT_FIELD_NAME, QVariant.Double), 3 : QgsField(AREA_FIELD_NAME, QVariant.Double), 4 : QgsField(HT_FIELD_NAME, QVariant.Int), } output_file = '%sfpc_%s.shp' % (self._tmp_dir, get_unique_filename()) logAPICall.log('create outputfile %s ... ' % output_file, logAPICall.DEBUG) try: writer = QgsVectorFileWriter(output_file, "utf-8", fields, QGis.WKBPoint, self._crs, "ESRI Shapefile") f = QgsFeature() gid = 0 for _f in layer_features(tmp_fp_layer): # NOTE: geom.transform does projection in place to underlying # C object, for some reason, multiple projection does not # work correctly. following is a work-around # 1. get geometry geom = _f.geometry() # 2. get original centroid point and project is required centroid = geom.centroid().asPoint() if transform_required: t_centroid = transform.transform(centroid) else: t_centroid = centroid # 3. project into mercator and get area in m2 geom.transform(mercator_transform) area = geom.area() # write to file gid += 1 f.setGeometry(QgsGeometry.fromPoint(t_centroid)) f.addAttribute(0, QVariant(gid)) f.addAttribute(1, QVariant(t_centroid.x())) f.addAttribute(2, QVariant(t_centroid.y())) f.addAttribute(3, QVariant(area)) if ht_idx != -1: f.addAttribute(4, _f.attributeMap()[ht_idx]) else: f.addAttribute(4, QVariant(0)) writer.addFeature(f) del writer, f except Exception as err: remove_shapefile(output_file) raise OperatorError("error creating footprint centroids: %s" % err, self.__class__) fp_layer = load_shapefile(output_file, tmp_fp_layername) if not fp_layer: raise OperatorError('Error loading footprint centroid file' % (output_file), self.__class__) # clean up del tmp_fp_layer # store data in output self.outputs[0].value = fp_layer self.outputs[1].value = output_file