10
\$\begingroup\$

I have written a Django app that would get some input and give you an image as output. The output image has 3 layers:

  • Google Map image
  • rosreestr (russian docs layer)
  • data from geojson layer

I hear that I write code like a junior developer, so please help me improve, especially regarding clear code and architecture. (I am also interested in learning Erlang or Scala.)

Full code on GitHub

concat.py

from cStringIO import StringIO
import PIL
def concat_images(image, layer, rosreestr=None):
 """
 function that concat png images like sandwich
 used for concat google map static image and
 mapnik layers image
 image - png image google map
 layer - png mapnik image
 """
 buf = StringIO()
 if rosreestr:
 rosreestr = rosreestr.resize(image.size)
 image = PIL.Image.alpha_composite(image, rosreestr)
 PIL.Image.alpha_composite(image, layer).save(buf, 'PNG')
 buf.seek(0)
 return buf

consts.py

from pyproj import Proj
from math import pi
EARTH_RADIUS = 6378137
EQUATOR_CIRCUMFERENCE = 2 * pi * EARTH_RADIUS
INITIAL_RESOLUTION = EQUATOR_CIRCUMFERENCE / 256.0
ORIGIN_SHIFT = EQUATOR_CIRCUMFERENCE / 2.0
MAP_SRS = '+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 '
MAP_SRS += '+lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 '
MAP_SRS += '+units=m +nadgrids=@null +wktext +no_defs'
EPSG4326 = 'epsg:4326'
EPSG3857 = 'epsg:3857'
IN_PROJ = Proj(init=EPSG4326)
OUT_PROJ = Proj(init=EPSG3857)
TMP_DIR = 'tmp'
TMP_GEOJSON = 'tmp.geojson'

exceptions.py

class GeometryTypeError(Exception):
 pass

map_filler.py

import mapnik
from mapnik._mapnik import DataGeometryType
from pyproj import transform
import PIL
import os
import json
from tempfile import NamedTemporaryFile
from . import utils
from . import consts
from .exceptions import GeometryTypeError
def create_symbolizer(datasource):
 """
 symbolizer fabric for various types of shape
 """
 geom_type = datasource.geometry_type()
 if geom_type == DataGeometryType.Point:
 symbolizer = mapnik.PointSymbolizer()
 elif geom_type == DataGeometryType.Polygon:
 symbolizer = mapnik.PolygonSymbolizer()
 elif geom_type == DataGeometryType.LineString:
 symbolizer = mapnik.LineSymbolizer()
 elif geom_type == DataGeometryType.Collection:
 symbolizer = mapnik.LineSymbolizer()
 else:
 msg = 'Invalid geomerty type of object %s' % datasource
 raise GeometryTypeError(msg)
 return symbolizer
class MapFiller(mapnik.Map):
 """
 mapnik.Map object that create map with included styles,
 datasource, coordinates
 """
 styles = {'stroke': 'color',
 'fill': 'color',
 'fill_opacity': 'opacity',
 'stroke_opacity': 'opacity',
 'opacity': 'opacity',
 'stroke_width': 'weight',
 'width': 'weight'}
 def __init__(self, imager, **kwargs):
 self.upperleft = imager.upperleft
 self.lowerright = imager.lowerright
 box_xy = self.create_valid_box(imager)
 super(MapFiller, self).__init__(*box_xy, **kwargs)
 self.srs = consts.MAP_SRS
 if not os.path.isdir(consts.TMP_DIR):
 os.mkdir(consts.TMP_DIR)
 def create_valid_box(self, imager):
 self.dx = int(imager.dx)
 self.dy = int(imager.dy)
 return (self.dx, self.dy)
 def filling_map(self, layers):
 for i in range(len(layers)):
 self.append_layer(layers[i], i)
 def append_layer(self, layer, i):
 self.correct_layer_geom(layer)
 geom = layer.get('geom')
 if not geom:
 return
 layer_geojson = self.create_geojson(geom)
 filename = os.path.join(consts.TMP_DIR, consts.TMP_GEOJSON)
 datasource = self.write_datasource(filename, layer_geojson)
 symbolizer = create_symbolizer(datasource)
 self.set_style(symbolizer, layer['style'])
 name = 'style%s' % str(i)
 self.push_style(name, symbolizer)
 self.push_layer(name, datasource)
 def correct_layer_geom(self, layer):
 for j in range(len(layer.get('geom', []))):
 if layer['geom'][j]:
 layer['geom'][j]['coordinates'] = \
 self.epsg4326_to_3857(layer['geom'][j])
 def create_geojson(self, geom):
 geojson = {"type": "FeatureCollection"}
 geojson['features'] = [{"type": "Feature",
 "geometry": coord,
 "properties": {}} for coord in geom]
 return geojson
 def write_datasource(self, filename, geojson):
 with open(filename, 'w') as f:
 f.write(json.dumps(geojson))
 datasource = mapnik.Datasource(type='geojson', file=filename)
 os.remove(filename)
 return datasource
 def push_layer(self, name, datasource):
 new_layer = mapnik.Layer(name)
 new_layer.datasource = datasource
 new_layer.srs = consts.MAP_SRS
 new_layer.styles.append(name)
 self.layers.append(new_layer)
 def push_style(self, name, symbolizer):
 style = mapnik.Style()
 rule = mapnik.Rule()
 rule.symbols.append(symbolizer)
 style.rules.append(rule)
 self.append_style(name, style)
 def epsg4326_to_3857(self, coordinates):
 if coordinates['type'] == 'Point':
 return self.transform_point(coordinates['coordinates'])
 elif coordinates['type'] == 'MultiLineString' or \
 coordinates['type'] == 'Polygon':
 return self.list_comp_map(coordinates['coordinates'])
 elif coordinates['type'] == 'LineString' or \
 coordinates['type'] == 'MultiPoint':
 coords = coordinates['coordinates']
 return [self.transform_point(p) for p in coords]
 elif coordinates['type'] == 'MultiPolygon':
 return map(lambda cc: self.list_comp_map(cc),
 coordinates['coordinates'])
 else:
 msg = 'Invalid geomerty type of json object by database'
 raise GeometryTypeError(msg)
 def set_style(self, sym, params):
 for k, v in self.styles.iteritems():
 if hasattr(sym, k) and v in params:
 if v == 'color':
 setattr(sym, k, mapnik.Color(str(params[v])))
 else:
 setattr(sym, k, float(params[v]))
 return sym
 def render_map(self):
 self.map_tmp_file = NamedTemporaryFile()
 mapnik.render_to_file(self,
 self.map_tmp_file.name,
 'png')
 def zoom_to_layers_box(self):
 box = self.create_box(self.upperleft, self.lowerright)
 self.zoom_to_box(box)
 def create_box(self, upperleft, lowerright):
 upperleft, lowerright = utils.get_coords(upperleft, lowerright, False)
 upperleft = MapFiller.transform_point(upperleft)
 lowerright = MapFiller.transform_point(lowerright)
 coords = upperleft + lowerright
 return mapnik.Box2d(*coords)
 @staticmethod
 def transform_point(coords):
 return list(transform(consts.IN_PROJ,
 consts.OUT_PROJ,
 *coords))
 def list_comp_map(self, list_of_points):
 return map(lambda c: [self.transform_point(p) for p in c],
 list_of_points)

map_imager.py

from cStringIO import StringIO
from math import ceil
import urllib
from PIL import Image
from . import utils
class BaseMapImager(object):
 zoom = None
 upperleft = None
 lowerright = None
 maxsize = 450
 scale = 1
 bottom = 0
 encoded_delimeter = '%2C'
 def __init__(self, *args, **kwargs):
 self.unpack_kwargs(**kwargs)
 self.valid_params()
 self.set_coords_angles_of_image()
 def valid_params(self):
 if not hasattr(self, 'upperleft') or not hasattr(self, 'lowerright'):
 raise Exception('Not enough params, need lowerright and upperleft')
 def unpack_kwargs(self, **kwargs):
 for key in kwargs:
 setattr(self, key, kwargs[key])
 def init_image(self):
 self.create_parent_image()
 self.fill_image()
 return self.parent_image
 def create_parent_image(self):
 size = (int(self.dx), int(self.dy))
 self.parent_image = Image.new("RGBA", size)
 def load_image(self, url):
 f = urllib.urlopen(url)
 return Image.open(StringIO(f.read()))
 def fill_image(self):
 for x in range(self.cols):
 for y in range(self.rows):
 self.fill_in_position(x, y)
 def set_coords_angles_of_image(self):
 ullat, ullon = map(float, self.upperleft.split(','))
 lrlat, lrlon = map(float, self.lowerright.split(','))
 self.coords = {'upperleft_lat': ullat,
 'upperleft_lon': ullon,
 'lowerright_lat': lrlat,
 'lowerright_lon': lrlon}
class MapImager(BaseMapImager):
 def __init__(self, *args, **kwargs):
 super(MapImager, self).__init__(*args, **kwargs)
 self.set_size_of_image()
 self.get_cols_rows()
 self.set_sizes_of_chunk(self.bottom)
 def set_size_of_image(self):
 coords = self.coords
 upperleft_x_y = utils.latlontopixels(coords['upperleft_lat'],
 coords['upperleft_lon'],
 self.zoom)
 self.upperleft_x, self.upperleft_y = upperleft_x_y
 lowerright_x_y = utils.latlontopixels(coords['lowerright_lat'],
 coords['lowerright_lon'],
 self.zoom)
 self.lowerright_x, self.lowerright_y = lowerright_x_y
 self.dx = self.lowerright_x - self.upperleft_x
 self.dy = self.upperleft_y - self.lowerright_y
 def get_cols_rows(self):
 self.cols = int(ceil(self.dx / self.maxsize))
 self.rows = int(ceil(self.dy / self.maxsize))
 def set_sizes_of_chunk(self, bottom):
 self.largura = int(ceil(self.dx / self.cols))
 self.altura = int(ceil(self.dy / self.rows))
 self.alturaplus = self.altura + bottom
 def set_position(self, x, y):
 dxn = self.largura * (0.5 + x)
 dyn = self.altura * (0.5 + y)
 px = self.upperleft_x + dxn
 py = self.upperleft_y - dyn - self.bottom / 2
 latn, lonn = utils.pixelstolatlon(px, py, self.zoom)
 return self.latn_lonn_to_string(latn, lonn)
 def latn_lonn_to_string(self, latn, lonn):
 return ','.join((str(latn), str(lonn)))
 def fill_in_position(self, x, y):
 position = self.set_position(x, y)
 urlparams = self.get_url_params(position)
 url = self.url + urlparams
 image_inst = self.load_image(url)
 self.parent_image.paste(image_inst,
 (int(x * self.largura),
 int(y * self.altura)))
class GoogleImager(MapImager):
 """
 interprate for google
 """
 url = 'http://maps.google.com/maps/api/staticmap?'
 maxsize = 640
 def get_url_params(self, position):
 url = urllib.urlencode({'center': position,
 'zoom': str(self.zoom),
 'size': '%dx%d' % (self.largura,
 self.alturaplus),
 'maptype': self.map_type,
 'scale': self.scale})
 return url.replace(self.encoded_delimeter, ',')
class YandexImager(MapImager):
 url = 'https://static-maps.yandex.ru/1.x/?'
 maxsize = 450
 # def load_image(url):
 # return urllib.urlopen(url)
 def latn_lonn_to_string(self, latn, lonn):
 return ','.join((str(lonn), str(latn)))
 def get_url_params(self, position):
 url = urllib.urlencode({'ll': position,
 'z': str(self.zoom),
 'size': '%d,%d' % (self.largura,
 self.alturaplus),
 'l': self.map_type,
 'scale': self.scale})
 return url.replace(self.encoded_delimeter, ',')
class GoogleMapImager(GoogleImager):
 map_type = 'roadmap'
class GoogleSatImager(GoogleImager):
 map_type = 'satellite'
class YandexMapImager(YandexImager):
 map_type = 'map'
class YandexSatImager(YandexMapImager):
 map_type = 'sat'
class TwoGisMapImager(MapImager):
 url = 'http://static.maps.2gis.com/1.0?'
 maxsize = 1200
 def latn_lonn_to_string(self, latn, lonn):
 return ','.join((str(lonn), str(latn)))
 def get_url_params(self, position):
 url = urllib.urlencode({'center': position,
 'zoom': str(self.zoom),
 'size': '%d,%d' % (self.largura,
 self.alturaplus)})
 return url.replace(self.encoded_delimeter, ',')
class OSMMapImager(MapImager):
 access_token = 'pk.eyJ1IjoiZGVubnk1MzEiLCJhIjoiY2l3NHhlbjkwMDAwcTJ0bzRzc3p0bmNxaCJ9.QG39g1_q4GANnTPVIizKEg'
 url = 'https://api.mapbox.com/v4/mapbox.emerald/'
 maxsize = 1280
 def latn_lonn_to_string(self, latn, lonn):
 return ','.join((str(lonn), str(latn)))
 def get_url_params(self, position):
 url = '%s,%s/%sx%s.png?' % (position,
 self.zoom,
 self.largura,
 self.altura)
 url += urllib.urlencode({'access_token': self.access_token})
 return url.replace(self.encoded_delimeter, ',')
class RosreestrImager(MapImager):
 maxsize = 2048
 url = 'http://pkk5.rosreestr.ru/arcgis/rest/services/Cadastre/Cadastre/MapServer/export?'
 layers = 'show:0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24'
 bboxSR = 4326
 imageSR = 3857
 size = '2048,2048'
 format = 'png24'
 transparent = True
 f = 'image'
 dpi = 15
 bbox = None
 def __init__(self, *args, **kwargs):
 super(RosreestrImager, self).__init__(*args, **kwargs)
 self.calculate_numbers_of_chunks()
 self.calculate_delta_coords_image()
 def calculate_delta_coords_image(self):
 self.calculate_delta_lon()
 self.calculate_delta_lat()
 def calculate_delta_lon(self):
 lowerright = self.coords['lowerright_lon']
 upperleft = self.coords['upperleft_lon']
 self.delta_lon = abs(upperleft - lowerright) / \
 self.number_of_chunks
 def calculate_delta_lat(self):
 lowerright = self.coords['lowerright_lat']
 upperleft = self.coords['upperleft_lat']
 self.delta_lat = abs(upperleft - lowerright) / \
 self.number_of_chunks
 def latn_lonn_to_string(self, latn, lonn):
 return ','.join((str(lonn), str(latn)))
 def get_image_size(self):
 return '%s,%s' % (self.maxsize, self.maxsize)
 def calculate_numbers_of_chunks(self):
 self.number_of_chunks = self.detail_level + 1
 def set_position(self, x, y):
 ullon = self.coords['upperleft_lon'] + self.delta_lon * x
 lrlon = ullon + self.delta_lon * (x + 1)
 ullat = self.coords['upperleft_lat'] + self.delta_lon * y
 lrlat = ullat + self.delta_lat * (y + 1)
 return '%s,%s,%s,%s' % (ullon, ullat, lrlon, lrlat)
 def get_url_params(self, position):
 url = urllib.urlencode({'layers': self.layers,
 'bboxSR': self.bboxSR,
 'imageSR': self.imageSR,
 'size': self.get_image_size(),
 'format': self.format,
 'transparent': self.transparent,
 'f': self.f,
 'dpi': self.dpi,
 'bbox': position})
 return url.replace(self.encoded_delimeter, ',')
def select_map_image(name):
 map_hash = {'google_map': GoogleMapImager,
 'google_sat': GoogleSatImager,
 'yandex_map': YandexMapImager,
 'yandex_sat': YandexSatImager,
 '2gis': TwoGisMapImager,
 'osm': OSMMapImager}
 return map_hash[name]
def create_map_image(map_lay):
 map_lay.render_map()
 return PIL.Image.open(map_lay.map_tmp_file.name)

rosreestr.py

import urllib
from PIL import Image
from .map_imager import BaseMapImager
class RosreestrImager(BaseMapImager):
 # longitude binded to x coordinates
 # latitude to y
 maxsize = 2048
 url = 'http://pkk5.rosreestr.ru/arcgis/rest/services/Cadastre/Cadastre/MapServer/export?'
 layers = 'show:0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24'
 bboxSR = 4326
 imageSR = 3857
 size = '2048,2048'
 format = 'png24'
 transparent = True
 f = 'image'
 dpi = 15
 bbox = None
 def __init__(self, **kwargs):
 super(RosreestrImager, self).__init__(**kwargs)
 self.normalize_angles()
 self.calculate_bbox()
 self.calculate_numbers_of_chunks()
 self.calculate_delta_coords_image()
 self.create_parent_image()
 def normalize_angles(self):
 self.normalize_angle('lowerright')
 self.normalize_angle('upperleft')
 def normalize_angle(self, name):
 angle_attr = getattr(self, name)
 tmp = angle_attr.split(',')
 tmp.reverse()
 setattr(self, name, ','.join(tmp))
 def calculate_bbox(self):
 self.bbox = self.upperleft + ',' + self.lowerright
 def calculate_numbers_of_chunks(self):
 self.number_of_chunks = self.detail_level + 1
 def calculate_delta_coords_image(self):
 self.calculate_delta_lon()
 self.calculate_delta_lat()
 def calculate_delta_lon(self):
 lowerright = self.coords['lowerright_lon']
 upperleft = self.coords['upperleft_lon']
 self.delta_lon = abs(upperleft - lowerright) / \
 self.number_of_chunks
 def calculate_delta_lat(self):
 lowerright = self.coords['lowerright_lat']
 upperleft = self.coords['upperleft_lat']
 self.delta_lat = abs(upperleft - lowerright) / \
 self.number_of_chunks
 def create_parent_image(self):
 self.parent_image = Image.new("RGBA")
 def create_image(self):
 for x in xrange(1, self.number_of_chunks + 1):
 for y in xrange(1, self.number_of_chunks + 1):
 self.create_chunk_image(x, y)
 def create_chunk_image(self, x, y):
 bbox = self.calculate_bbox_chunk(x, y)
 urlparams = self.get_url_params(bbox)
 url = self.url + urlparams
 img = self.load_image(url)
 def calculate_bbox_chunk(self, x, y):
 ullon = self.coords['upperleft_lon']
 lrlon = ullon + self.delta_lon * x
 ullat = self.coords['upperleft_lat']
 lrlat = ullat + self.delta_lat * y
 return '%s,%s,%s,%s' % (ullon, ullat, lrlon, lrlat)
 def init_image(self):
 urlparams = self.get_url_params()
 url = self.url + urlparams
 return self.load_image(url)
 def get_url_params(self, bbox=None):
 url = urllib.urlencode({'layers': self.layers,
 'bboxSR': self.bboxSR,
 'imageSR': self.imageSR,
 'size': self.get_image_size(),
 'format': self.format,
 'transparent': self.transparent,
 'f': self.f,
 'dpi': self.dpi,
 'bbox': bbox or self.bbox})
 return url.replace(self.encoded_delimeter, ',')
 def get_image_size(self):
 return '%s,%s' % (self.maxsize, self.maxsize)

utils.py

from math import pi, log, tan, atan, exp
import urllib2
import json
from . import consts
from .exceptions import GeometryTypeError
def latlontopixels(lat, lon, zoom):
 mx = (lon * consts.ORIGIN_SHIFT) / 180.0
 my = log(tan((90 + lat) * pi / 360.0)) / (pi / 180.0)
 my = (my * consts.ORIGIN_SHIFT) / 180.0
 res = consts.INITIAL_RESOLUTION / (2**zoom)
 px = (mx + consts.ORIGIN_SHIFT) / res
 py = (my + consts.ORIGIN_SHIFT) / res
 return px, py
def pixelstolatlon(px, py, zoom):
 """
 convert resolution of image to coordinates
 """
 res = consts.INITIAL_RESOLUTION / (2**zoom)
 mx = px * res - consts.ORIGIN_SHIFT
 my = py * res - consts.ORIGIN_SHIFT
 lat = (my / consts.ORIGIN_SHIFT) * 180.0
 lat = 180 / pi * (2 * atan(exp(lat * pi / 180.0)) - pi / 2.0)
 lon = (mx / consts.ORIGIN_SHIFT) * 180.0
 return lat, lon
def get_coords(upperleft, lowerright, concat=True):
 """
 convert bounds coordinates strings to array of coordinates
 """
 upperleft = coords_string_to_float(upperleft, reverse=True)
 # upperleft = coords_string_to_float(upperleft)
 lowerright = coords_string_to_float(lowerright, reverse=True)
 if concat:
 return upperleft + lowerright
 else:
 return upperleft, lowerright
def coords_string_to_float(coord, reverse=False):
 """
 string coordiantes to float
 """
 values = coord.split(',')
 result = [float(v) for v in values]
 if reverse:
 result.reverse()
 return result
def map_geom_data(obj):
 """
 from url and styles hash
 create object that include
 geomatry of object and styles
 """
 response = urllib2.urlopen(obj['url']).read()
 response = json.loads(response)
 layer = {}
 if 'features' in response:
 layer['geom'] = [geom['geometry'] for geom in response['features']]
 else:
 msg = 'Invalid geojson'
 raise GeometryTypeError(msg)
 if 'style' in obj:
 layer['style'] = obj['style']
 else:
 layer['style'] = {}
 return layer

views.py

# -*- coding: utf-8 -*-
from django.http import HttpResponse
from django.views.generic import View
import json
from .map_imager import select_map_image, create_map_image, RosreestrImager
from .map_filler import MapFiller
from . import utils
from .concat import concat_images
from .mixins import respond_as_attachment
class PrintLayView(View):
 def get(self, request, *args, **kwargs):
 self.init_data(request)
 if self.include_rosreestr:
 self.create_rosreestr_image()
 try:
 self.create_map_image()
 except IOError:
 return HttpResponse('Произошла ошибка. Попробуй задать меньше "Максимальный размер плитки (тайла)"')
 self.create_lay_image()
 image_stream = concat_images(self.img, self.lay, self.img_rosreestr)
 return respond_as_attachment(request, image_stream)
 def init_data(self, request):
 self.img_rosreestr = None
 self.data = json.loads(request.GET['data'])
 self.valid_data()
 self.unpack_data()
 def valid_data(self):
 if 'layersProps' not in self.data:
 return HttpResponse('Data is not a valid, need `layersProps`')
 if 'mapName' not in self.data:
 return HttpResponse('Data is not a valid, need `mapName`')
 def unpack_data(self):
 self.layers_props = json.loads(self.data['layersProps'])
 self.upperleft = self.data['upperleft']
 self.lowerright = self.data['lowerright']
 self.detail_level = int(self.data['detailLevel'])
 self.zoom = int(self.data['zoom'])
 self.zoom += self.detail_level
 self.map_name = self.data['mapName']
 self.include_rosreestr = self.data['includeRosreestr']
 def create_rosreestr_image(self):
 rosreestr_imager = RosreestrImager(upperleft=self.upperleft,
 lowerright=self.lowerright,
 detail_level=self.detail_level,
 zoom=self.zoom)
 self.img_rosreestr = rosreestr_imager.init_image()
 def create_map_image(self):
 map_imager = select_map_image(self.map_name)
 self.imager = map_imager(upperleft=self.upperleft,
 lowerright=self.lowerright,
 zoom=self.zoom)
 self.img = self.imager.init_image()
 def create_lay_image(self):
 layers = map(utils.map_geom_data,
 self.layers_props)
 Map = MapFiller(self.imager)
 Map.filling_map(layers)
 Map.zoom_to_layers_box()
 self.lay = create_map_image(Map)

mixins.py

from django.http import HttpResponse
import mimetypes
import os
import urllib
import uuid
def respond_as_attachment(request, file_stream):
 """
 mixin that return file like stream
 """
 original_filename = str(uuid.uuid4()) + '.png'
 response = HttpResponse(file_stream.read())
 file_stream.seek(0, os.SEEK_END)
 response['Content-Length'] = file_stream.tell()
 file_stream.close()
 type, encoding = mimetypes.guess_type(original_filename)
 if type is None:
 type = 'application/octet-stream'
 response['Content-Type'] = type
 if encoding is not None:
 response['Content-Encoding'] = encoding
 if 'WebKit' in request.META['HTTP_USER_AGENT']:
 filename_header = 'filename=%s' % original_filename.encode('utf-8')
 elif 'MSIE' in request.META['HTTP_USER_AGENT']:
 filename_header = ''
 else:
 encoded_name = original_filename.encode('utf-8')
 filename_header = \
 'filename*=UTF-8\'\'%s' % urllib.quote(encoded_name)
 response['Content-Disposition'] = 'attachment; ' + filename_header
 return response
asked Jun 1, 2017 at 20:49
\$\endgroup\$
4
  • 1
    \$\begingroup\$ if you have this type of thing going on in your code if type == square then do this elseif type ==circle then do that, if you are checking for types and then responding - that's a no-no in OOP. it means you are missing a duck type. \$\endgroup\$ Commented Jun 1, 2017 at 23:13
  • \$\begingroup\$ here this may help: bkspurgeon.github.io/BKSpurgeon.github.io/duck-types \$\endgroup\$ Commented Jun 1, 2017 at 23:54
  • \$\begingroup\$ Thanks you, but I can't inherite from this geometry type classes, I may just appropriate lambda function for for every of this type, but I think ii's not a better idea. Or not? \$\endgroup\$ Commented Jun 2, 2017 at 22:30
  • \$\begingroup\$ here is another option: gist.github.com/BKSpurgeon/8d32e295278236f439cbe80ac332df6e \$\endgroup\$ Commented Jun 3, 2017 at 0:38

1 Answer 1

1
\$\begingroup\$

I agree with BKSpurgeon's duck type criticism of create_symbolizer(). (Also, typo in msg, with possible copy-n-paste error into epsg4326_to_3857().)

In consts.py MAP_SRS, I see +a=6378137 +b=6378137, but was hoping to see +a=%d +b=%d with string formatting filling them in from EARTH_RADIUS.

Otherwise, it looks pretty good.

answered Aug 5, 2017 at 5:25
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.