mapnik切图和地图分辨率与比例尺

发布时间 2023-12-04 15:47:41作者: ZN大叔

mapbox 瓦片相关:  地址

osm瓦片相关:   地址

bingMAP瓦片相关:  地址

 

#include <mapnik/map.hpp>
#include <mapnik/load_map.hpp>
#include <mapnik/agg_renderer.hpp>
#include <mapnik/version.hpp>
#include <mapnik/debug.hpp>
#include <mapnik/image_util.hpp>
#include <mapnik/unicode.hpp>
#include <mapnik/datasource_cache.hpp>
#include <mapnik/font_engine_freetype.hpp>
#include <mapnik/projection.hpp>
#include <mapnik/proj_transform.hpp>
#include <math.h>
#pragma GCC diagnostic push
#include <mapnik/warning_ignore.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/program_options.hpp>
#pragma GCC diagnostic pop
#include <stdlib.h>
#include <string>
#include <sstream>
//转换经纬度为瓦片左上角经纬度
//左上为0,0原点的 xyz转wgs84 googl瓦片(目前使用)
//左下为0,0原点的 xyz TMS瓦片,需要反转Y轴,再使用
//赤道与0度经线为0,0原点的 xyz 百度瓦片。百度的图片像素坐标起点为左下角(目前不支持)
//墨卡托坐标系:展开地球,赤道作为x轴,向东为x轴正方,本初子午线作为y轴,向北为y轴正方向。
//魔卡托 地球半径 单位m
static const double EARTH_LENGTH = 6378137.0;
//魔卡托 地球的周长(赤道)的一半 20037508.342789244 单位米 周长2*pi*R 墨卡托坐标x轴区间[-20037508.3427892,20037508.3427892]
static const double circumferenceHalf = M_PI * 2 * EARTH_LENGTH / 2.0;
//图片像素
static const int img_pixel=256;
//屏幕dpi
static const int screen_dpi=96;
//(tileSize * pow(2, zoom) map width = map height = 256 * 2 ^level pixels  zoom级别时 一张瓦片代表地图的像素数
/***
 * 地图比例尺resolution
 * @param zoom z方向层级 从0级开始
 * @param tileSize 图片尺寸
 * @return 地图分辨率
 */
double z2resolution (double zoom, int tileSize = 256) {
    // 米/每像素
    auto resolution = M_PI * 2 * EARTH_LENGTH / (tileSize * pow(2, zoom));
    return resolution;
}
/*
 * 图像分辨率表示的单位inch所包含的像素点数,即PPI(Pixels Per Inch)或者DPI(Dots Per Inch)。一般地图默认DPI为96。
也有用图元来描述屏幕的可分辨率,如wmts1.0.0中像元大小为0.00028m来界定,这样1inch = 0.0254m
0.0254/0.00028 ≈ 90.71。
 */
/***
 * 根据地图分辨率计算比例尺
 * @param resolution  地图分辨率
 * @param dpi DPI PPI屏幕分辨率 每英寸 mapnik默认ppi 90.71 google 72
 * @return 比例尺
 */
double resolution2scale(double resolution,double dpi=96)
{
    return 0.0254/(resolution*dpi);
}
/***
 * 比例尺转比例尺分母
 * @param scale 比例尺
 * @return 比例尺分母
 */
double scale2scaleDenominator(double scale)
{
    return 1.0/scale;
}
int long2tilex(double lon, int z)
{
    return (int)(floor((lon + 180.0) / 360.0 * pow(2.0,z)));
}

int lat2tiley(double lat, int z)
{
    double latrad = lat * M_PI/180.0;
    return (int)(floor((1.0 - asinh(tan(latrad)) / M_PI) / 2.0 * pow(2.0,z)));
}

double tilex2long(int x, int z)
{
    return x / pow(2.0,z) * 360.0 - 180;
}

double tiley2lat(int y, int z)
{
    double n = M_PI - (2.0 * M_PI * y )/ pow(2.0,z);
    return 180.0 / M_PI * atan(0.5 * (exp(n) - exp(-n)));
}
/**
 * 反转y轴,用于将左上原点转换到左下原点
 * @param y 左上原点Y值
 * @param z 层级
 * @return 左下原点Y值
 */
int tiley2reversal(int y,int z)
{
    int ext = (int) pow(2, z);
    int changeRow = ext - y - 1;
    return changeRow;
}

/***
 * 计算z代表的经纬度范围
 * @param z xyz层级0-n
 * @return 当前层级瓦片的width 单位deg
 */
double zlevel2tilewidth(int z)
{
    int ext = (int)pow(2,z);
    double tilewidth = 360.0/ext;
    return tilewidth;
}
/***
 * 将xyz转换为瓦片经纬度bobox
 * @param x
 * @param y
 * @param z
 * @return
 */
mapnik::box2d<double> xyz2box(int x,int y,int z)
{
    auto min_lng = tilex2long(x,z);
    auto min_lat = tiley2lat(y+1,z);
    auto max_lng = tilex2long(x+1,z);
    auto max_lat = tiley2lat(y,z);


    mapnik::box2d<double> boundingBox;
    boundingBox.set_minx(min_lng);
    boundingBox.set_maxy(max_lat);
    boundingBox.set_miny(min_lat);
    boundingBox.set_maxx(max_lng);
    return boundingBox;
}

int main (int argc,char** argv)
{
//    if(argc != 4)
//        return -1;
//    int x=strtol(argv[0], nullptr,10);
//    int y=strtol(argv[1], nullptr,10);
//    int z=strtol(argv[2], nullptr,10);
//    auto xml_path=argv[3];
    namespace po = boost::program_options;

    bool verbose = false;
    bool auto_open = false;
    int return_value = 0;
    int x=0,y=0,z=0;
    std::string xml_file;
    std::string img_file;
    double scale_factor = 1;
    bool params_as_variables = false;
    auto to = mapnik::projection("+init=epsg:3857");
    auto from = mapnik::projection("+init=epsg:4326");
    mapnik::proj_transform tr(from,to);
    mapnik::logger logger;
    logger.set_severity(mapnik::logger::error);


    try
    {
        po::options_description desc("mapnik-render utility");
        desc.add_options()
            ("help,h", "produce usage message")
            ("version,V","print version string")
            ("verbose,v","verbose output")
            ("open","automatically open the file after rendering")
            ("xml",po::value<std::string>(),"xml map to read")
            ("img",po::value<std::string>(),"image to render")
            ("scale-factor",po::value<double>(),"scale factor for rendering")
            ("variables","make map parameters available as render-time variables")
                ("x",po::value<int>(),"xyz")
                ("y",po::value<int>(),"xyz")
                ("z",po::value<int>(),"xyz")
            ;

        po::positional_options_description p;
        p.add("xml",1);
        p.add("img",1);
        p.add("x",1);
        p.add("y",1);
        p.add("z",1);
        po::variables_map vm;
        po::store(po::command_line_parser(argc, argv).options(desc).positional(p).run(), vm);
        po::notify(vm);

        if (vm.count("version"))
        {
            std::clog <<"version " << MAPNIK_VERSION_STRING << std::endl;
            return 1;
        }

        if (vm.count("help"))
        {
            std::clog << desc << std::endl;
            return 1;
        }

        if (vm.count("verbose"))
        {
            verbose = true;
        }

        if (vm.count("open"))
        {
            auto_open = true;
        }

        if (vm.count("xml"))
        {
            xml_file=vm["xml"].as<std::string>();
            std::clog << "xmlPath:" << xml_file << std::endl;
        }
        else
        {
            std::clog << "please provide an xml map as first argument!" << std::endl;
            return -1;
        }

        if (vm.count("img"))
        {
            img_file=vm["img"].as<std::string>();
            std::clog << "img_file:" << img_file << std::endl;
        }
        else
        {
            std::clog << "please provide an img as second argument!" << std::endl;
            return -1;
        }

        if (vm.count("scale-factor"))
        {
            scale_factor=vm["scale-factor"].as<double>();
        }

        if (vm.count("variables"))
        {
            params_as_variables = true;
        }
        if (vm.count("x"))
        {
            x = vm["x"].as<int>();
            std::clog << "x:" << x << std::endl;
        }
        if (vm.count("y"))
        {
            y = vm["y"].as<int>();
            std::clog << "y:" << y << std::endl;
        }
        if (vm.count("z"))
        {
            z = vm["z"].as<int>();
            std::clog << "z:" << z << std::endl;
        }

        auto m_resolution = z2resolution(z,256);
        auto m_scale = resolution2scale(m_resolution,90.72);
        auto m_scaleDenominator = scale2scaleDenominator(m_scale);
        std::ostringstream out;
        out.precision(15);
        out << std::fixed << m_resolution;
        std::cout << "z level:===>"<<z<< std::endl;
        std::cout << "pie==>m_resolution:===>" << out.str()<< std::endl;
        out.str("");
        out <<m_scale;
        std::cout << "pie==>m_scale:===>" << out.str() << std::endl;
        out.str("");
        out <<m_scaleDenominator;
        std::cout << "pie==>m_scaleDenominator:===>" << out.str() << std::endl;
        out.str("");



        //mapnik::datasource_cache::instance().register_datasources("./plugins/input/");
        mapnik::freetype_engine::register_fonts("./fonts", true);
        mapnik::Map map(256,256,"+init=epsg:3857");

        mapnik::load_map(map,xml_file);
//        std::clog << "map.width" << map.width()<< std::endl;
//        std::clog << "map.height" << map.height()<< std::endl;

        //map.set_aspect_fix_mode(mapnik::Map::aspect_fix_mode::ADJUST_BBOX_HEIGHT);
        auto box = xyz2box(x,y,z);
//        std::clog << "xyz2box:minx" << box.minx() << std::endl;
//        std::clog << "xyz2box:miny" << box.miny() << std::endl;
//        std::clog << "xyz2box:maxx" << box.maxx() << std::endl;
//        std::clog << "xyz2box:maxy" << box.maxy() << std::endl;
        bool isok = tr.forward(box);
//        std::clog << "xyz2box:minx" << box.minx() << std::endl;
//        std::clog << "xyz2box:miny" << box.miny() << std::endl;
//        std::clog << "xyz2box:maxx" << box.maxx() << std::endl;
//        std::clog << "xyz2box:maxy" << box.maxy() << std::endl;
        map.zoom_to_box(box);
        out << map.scale_denominator();
        std::clog << "mapnik:scale_denominator===>" << out.str() << std::endl;
        out.str("");
        out << 1.0/map.scale_denominator();
        std::clog << "mapnik:scale===>" << out.str() << std::endl;
        out.str("");
        mapnik::image_rgba8 im(img_pixel,img_pixel);
        auto current_extent = map.get_current_extent();
        mapnik::request req(img_pixel,img_pixel,box);
//        std::clog << "get_current_extent:minx" << current_extent.minx() << std::endl;
//        std::clog << "get_current_extent:miny" << current_extent.miny() << std::endl;
//        std::clog << "get_current_extent:maxx" << current_extent.maxx() << std::endl;
//        std::clog << "get_current_extent:maxy" << current_extent.maxy() << std::endl;
//        std::clog << "buffer_size" << map.buffer_size() << std::endl;
        req.set_buffer_size(2);
        mapnik::attributes vars;
        if (params_as_variables)
        {
            mapnik::transcoder tr("utf-8");
            for (auto const& param : map.get_extra_parameters())
            {
                std::string const& name = param.first.substr(1);
                if (!name.empty())
                {
                    if (param.second.is<mapnik::value_integer>())
                    {
                        vars[name] = param.second.get<mapnik::value_integer>();
                    }
                    else if (param.second.is<mapnik::value_double>())
                    {
                        vars[name] = param.second.get<mapnik::value_double>();
                    }
                    else if (param.second.is<std::string>())
                    {
                        vars[name] = tr.transcode(param.second.get<std::string>().c_str());
                    }
                }
            }
        }
        mapnik::agg_renderer<mapnik::image_rgba8> ren(map,req,vars,im);
        ren.apply();
        std::clog << "begin save_to_file" << std::endl;
        std::string pngstr;
        std::ostringstream s;
        pngstr = mapnik::save_to_string(im,"png8");
        mapnik::save_to_stream(im,s,"png8");
        mapnik::save_to_file(im,img_file);
//        std::clog << "string:=========>" << std::endl;
//        std::clog << pngstr << std::endl;
//        std::clog << "stream:=========>" << std::endl;
//        std::clog << s.str() << std::endl;
        std::clog << "end save_to_file" << std::endl;
        if (auto_open)
        {
            std::ostringstream s;
#ifdef __APPLE__
            s << "open ";
#elif _WIN32
            s << "start ";
#else
            s << "xdg-open ";
#endif
            s << img_file;
            int ret = system(s.str().c_str());
            if (ret != 0)
                return_value = ret;
        }
        else
        {
            std::clog << "rendered to: " << img_file << "\n";
        }
    }
    catch (std::exception const& ex)
    {
        std::clog << "Error " << ex.what() << std::endl;
        return -1;
    }
    return return_value;
}