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
\$\endgroup\$
  • 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\$ – BKSpurgeon Jun 1 '17 at 23:13
  • \$\begingroup\$ here this may help: bkspurgeon.github.io/BKSpurgeon.github.io/duck-types \$\endgroup\$ – BKSpurgeon Jun 1 '17 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\$ – Denny Jun 2 '17 at 22:30
  • \$\begingroup\$ here is another option: gist.github.com/BKSpurgeon/8d32e295278236f439cbe80ac332df6e \$\endgroup\$ – BKSpurgeon Jun 3 '17 at 0:38
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.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.