Порт сервера на публичный сервер

Этот коммит содержится в:
Коммит 5c026601e1
63 изменённых файлов: 2381 добавлений и 0 удалений

21
LICENSE Обычный файл
Просмотреть файл

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 SWD Embedded Systems Ltd
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

30
README.md Обычный файл
Просмотреть файл

@ -0,0 +1,30 @@
# gis-map-server
Веб-сервис для распространения и тестовый клиент для отображения картографической информации.
[gis-map-server](src/gis-map-server/) - Python веб-сервер
[gis-client-render](src/gis-client-render) - Qt клиент для отображения
Архитектура проекта:
![GIS Map Server](src/gis-map-server/docs/architecture.drawio.png)
Сервис принимает запросы на отрисовку региона средствами [ПК ЦКИ](https://kpda.ru/products/gis/) и возвращает отрисованную карту в виде картинки (поддерживаемые выходные форматы: bmp, png, jpeg).
Для сборки необходимо:
```
cd src
# Единожды сформировать файл переводов
lrelease gis-client-render/*pro
# Для сборки под архитектуру _cpu_ можно установить CPULIST=_cpu_, например x86
make install
```
В результате в корне проекта появится папка `.install`, в которую инсталлируются runtime-компоненты
При запуске веб-сервер `gis-map-server` считывает [конфигурационный файл](src/gis-map-server/gis-map-server.conf).
`gis-map-server` предоставляет веб-интерфейс, внешний вид которого показан на рисунке 1.
![Рисунок 1](src/gis-map-server/docs/web_interface1.png)
Внешний вид Qt-клиента показан на рисунке 2.
![Рисунок 2](src/gis-client-render/images/client_interface1.png)

2
src/Makefile Обычный файл
Просмотреть файл

@ -0,0 +1,2 @@
LIST=LIB
include recurse.mk

2
src/gis-client-render/Makefile Обычный файл
Просмотреть файл

@ -0,0 +1,2 @@
LIST=OS
include recurse.mk

21
src/gis-client-render/common.mk Обычный файл
Просмотреть файл

@ -0,0 +1,21 @@
ifndef QCONFIG
QCONFIG=qconfig.mk
endif
include $(QCONFIG)
COMPILER_DRIVER:=
include $(MKFILES_ROOT)/buildlist.mk
ifndef OS
include $(MKFILES_ROOT)/qmacros.mk
endif
USEFILE=
# GIS package options
ifneq ( $(GIS_INSTALL_ROOT), )
DESTDIR:=$(GIS_INSTALL_ROOT)
endif
export GIS_INSTALL_ROOT=$(DESTDIR)
include $(MKFILES_ROOT)/qmake.mk
include $(MKFILES_ROOT)/qtargets.mk

51
src/gis-client-render/gis-client-render.pro Обычный файл
Просмотреть файл

@ -0,0 +1,51 @@
QT += core gui network
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
qnx {
# Get install directory for defining command to run
INSTALLDIRECTORY=$$(INSTALLDIR)
!isEmpty(INSTALLDIRECTORY) {
target.path = $$(INSTALLDIR)
}
isEmpty(INSTALLDIRECTORY) {
INSTALLDIRECTORY = "/opt/gis/bin"
target.path = $$INSTALLDIRECTORY
message(Manual path setting $$INSTALLDIRECTORY)
}
}
TEMPLATE = app
INSTALLS += target
# The following define makes your compiler emit warnings if you use
# any feature of Qt which as been marked as deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS
SOURCES += main.cpp\
mainwidget.cpp \
mainwindow.cpp \
image_viewport.cpp \
gismapserver.cpp \
mapsender.cpp
HEADERS += mainwidget.h \
mainwindow.h \
image_viewport.h \
gismapserver.h \
mapsender.h
# If: Cannot find file 'translations/gis-client-render_ru.qm'
# then run lupdate, lrelease manually
TRANSLATIONS += $$_PRO_FILE_PWD_/translations/$$join(TARGET,,,_ru.ts)
RESOURCES += \
images.qrc \
translations.qrc
QMAKE_CXXFLAGS += -std=gnu++11

52
src/gis-client-render/gismapserver.cpp Обычный файл
Просмотреть файл

@ -0,0 +1,52 @@
/*
* (c) 2011-2022, SWD Embedded Systems Limited, http://www.kpda.ru
*/
#include "gismapserver.h"
#include "mapsender.h"
QString gis_map_server_get_error_message( GisHttpCodes statusCode )
{
switch ( statusCode ) {
case GisHttpCodes::IsProcessing :
return "Is processing";
case GisHttpCodes::InvalidParameters :
return "Invalid parameters";
case GisHttpCodes::RequestFailed :
return "Failed to request";
case GisHttpCodes::Timeout :
return "Timeout while processing request";
case GisHttpCodes::RenderFailed :
return "Failed to render request";
case GisHttpCodes::Done :
return "Request has been already obtained";
case GisHttpCodes::Ok :
default:
return QString();
}
}
int gis_map_server_request_map( const char *server_url,
double lat,
double lon,
int scale,
int w,
int h,
const char *format,
uint *orderId,
char *pinCode )
{
if ( scale <= 0 || w <= 0 || h <= 0 || !orderId || !format )
return EINVAL;
uint timeout_ms = 30000;
MapSender mapSender( QString(server_url), 0, timeout_ms );
return mapSender.sendRequest( lat, lon, scale, w, h, format, orderId, pinCode );
}
int gis_map_server_get_image( const char *server_url, int orderId, char *pinCode, QImage *image )
{
uint timeout_ms = 30000;
MapSender mapSender( QString(server_url), orderId, timeout_ms );
return mapSender.getImageWithWaiting( pinCode, image );
}

44
src/gis-client-render/gismapserver.h Обычный файл
Просмотреть файл

@ -0,0 +1,44 @@
/*
* (c) 2011-2022, SWD Embedded Systems Limited, http://www.kpda.ru
*/
/*************************************************/
/* GIS Map Server */
/* Server interface header */
/*************************************************/
#ifndef GISMAPSENDER_H
#define GISMAPSENDER_H
#ifdef __cplusplus
#include <QImage>
#include <QString>
typedef enum {
Ok = 200,
IsProcessing = 202,
InvalidParameters = 400,
Timeout = 408,
Done = 410,
NoMemory = 418,
RenderFailed = 500,
RequestFailed = 520
} GisHttpCodes;
QString gis_map_server_get_error_message( GisHttpCodes statusCode );
/*
* Make a request to the gis-map-server
*
* format -png, jpeg, bmp
*/
int gis_map_server_request_map(const char *server_url, double lat, double lon, int scale, int w, int h, const char *format, uint* orderId, char *pinCode );
int gis_map_server_get_image(const char *server_url, int orderId, char *pinCode, QImage *image );
#endif
#endif // GISMAPSENDER_H

92
src/gis-client-render/image_viewport.cpp Обычный файл
Просмотреть файл

@ -0,0 +1,92 @@
/*
* (c) 2011-2022, SWD Embedded Systems Limited, http://www.kpda.ru
*/
#include "image_viewport.h"
#include <QGraphicsPixmapItem>
#include <QLabel>
#include <QMovie>
#include <QDebug>
ImageViewport::ImageViewport() : QGraphicsView(),
currentMapItem( nullptr )
{
/*
* Animated earth's GIF setup
* Runs until user performs a request
*
* See: https://forum.qt.io/topic/15658/solved-how-to-play-gif-animation-in-qgraphicsview-widget
*/
graphics_scene = new QGraphicsScene();
graphics_scene->setBackgroundBrush( QColor( 239, 239, 239 ) );
QLabel *animation = new QLabel();
QMovie *earth_gif = new QMovie( ":/images/earth.gif" );
animation->setMovie( earth_gif );
earth_gif->setSpeed( 90 );
earth_gif->start();
proxy = graphics_scene->addWidget( animation );
/*
* Placing a circle around GIF to hide its low resolution
*/
QPen earth_bound( (QColor( Qt::black )) );
earth_bound.setWidth( 5 );
graphics_scene->addEllipse( QRectF( 0, 0, 257, 257 ),
QPen( earth_bound ),
QBrush( QColor( 0, 0, 0, 0 ) ) );
setScene( graphics_scene );
setDragMode( QGraphicsView::ScrollHandDrag );
setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
}
void ImageViewport::fit_image()
{
/*
* Place the center of the image at the center of the viewport
*/
centerOn( graphics_scene->width() / 2,
graphics_scene->height() / 2 );
/*
* Keep the whole image inside the viewport, saving the aspect ratio
*/
fitInView( QRectF( 0, 0,
graphics_scene->width(),
graphics_scene->height() ),
Qt::KeepAspectRatio );
}
void ImageViewport::wheelEvent( QWheelEvent *event )
{
if ( event->delta() > 0 )
scale( scroll_step, scroll_step );
if ( event->delta() < 0 )
scale( 1 / scroll_step, 1 / scroll_step );
}
void ImageViewport::show_map_image( const QImage &image )
{
proxy->hide();
if ( currentMapItem )
graphics_scene->removeItem( currentMapItem );
currentMapItem = new QGraphicsPixmapItem( QPixmap::fromImage( image ) );
graphics_scene->addItem( currentMapItem );
fit_image();
}
void ImageViewport::clean_map_image()
{
proxy->show();
if ( currentMapItem ) {
graphics_scene->removeItem( currentMapItem );
currentMapItem = nullptr;
}
fit_image();
}

34
src/gis-client-render/image_viewport.h Обычный файл
Просмотреть файл

@ -0,0 +1,34 @@
/*
* (c) 2011-2022, SWD Embedded Systems Limited, http://www.kpda.ru
*/
#ifndef IMAGE_VIEWPORT_H
#define IMAGE_VIEWPORT_H
#include <QEvent>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QWheelEvent>
#include <QGraphicsProxyWidget>
class ImageViewport : public QGraphicsView
{
Q_OBJECT
public:
explicit ImageViewport();
void fit_image();
protected:
virtual void wheelEvent( QWheelEvent *event );
private:
const double scroll_step = 1.3;
QGraphicsScene *graphics_scene;
QGraphicsProxyWidget *proxy;
QGraphicsPixmapItem *currentMapItem;
public slots:
void show_map_image( const QImage &img );
void clean_map_image();
};
#endif // IMAGE_VIEWPORT_H

5
src/gis-client-render/images.qrc Обычный файл
Просмотреть файл

@ -0,0 +1,5 @@
<RCC>
<qresource prefix="/">
<file>images/earth.gif</file>
</qresource>
</RCC>

Двоичные данные
src/gis-client-render/images/client_interface1.png Обычный файл

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 435 KiB

Двоичные данные
src/gis-client-render/images/earth.gif Обычный файл

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 739 KiB

64
src/gis-client-render/main.cpp Обычный файл
Просмотреть файл

@ -0,0 +1,64 @@
/*
* (c) 2011-2022, SWD Embedded Systems Limited, http://www.kpda.ru
*/
#include <cstring>
#include <unistd.h>
#include <QApplication>
#include <QDebug>
#include <QTranslator>
#include "mainwidget.h"
#include "mainwindow.h"
int main( int argc, char *argv[] )
{
double x_pos = -999, y_pos = -999;
uint32_t width = 0, height = 0, scale = 0;
const char *server_url = nullptr;
const char *image_format = nullptr;
const char *outputImageName = nullptr;
bool consoleMode = false;
QLocale::setDefault(QLocale::C);
int opt = 0;
extern char *optarg;
while ( ( opt = getopt( argc, argv, "x:y:w:h:s:u:f:o:" ) ) != -1 )
{
switch ( opt )
{
case 'x': x_pos = atof( optarg ); break;
case 'y': y_pos = atof( optarg ); break;
case 'w': width = strtoul( optarg, NULL, 0 ); break;
case 'h': height = strtoul( optarg, NULL, 0 ); break;
case 's': scale = strtoul( optarg, NULL, 0 ); break;
case 'u': server_url = strdup( optarg ); break;
case 'f': image_format = strdup( optarg ); break;
case 'o':
{
consoleMode = true;
outputImageName = strdup( optarg );
break;
}
case '?': printf( "Error: unknown option\n" ); exit(1); break;
};
};
QApplication app( argc, argv );
QString ablangStr = getenv( "ABLANG" );
if ( ablangStr.isEmpty() ) {
ablangStr = getenv( "LANG" );
}
QTranslator myTranslator;
myTranslator.load( ":/translations/gis-client-render_" + ablangStr );
app.installTranslator( &myTranslator );
MainWindow window( x_pos, y_pos, width, height, scale, server_url, image_format, outputImageName, consoleMode );
window.show();
return app.exec();
}

264
src/gis-client-render/mainwidget.cpp Обычный файл
Просмотреть файл

@ -0,0 +1,264 @@
/*
* (c) 2011-2022, SWD Embedded Systems Limited, http://www.kpda.ru
*/
#include "mainwidget.h"
#include <QApplication>
#include <QDebug>
#include <QLabel>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QMutex>
#include <QWaitCondition>
#include <QDir>
#include "gismapserver.h"
MainWidget::MainWidget( double x_pos,
double y_pos,
uint32_t width,
uint32_t height,
uint32_t scale,
const char *server_url,
const char *image_format,
const char *outputImageName,
bool consoleMode ) : QWidget(),
_consoleMode( consoleMode ),
_outputImageName( outputImageName ),
_server_url( server_url ),
_image_format( image_format ),
request_btn_state( true ),
field_x_pos( new QLineEdit() ),
field_y_pos( new QLineEdit() ),
field_width( new QLineEdit() ),
field_height( new QLineEdit() ),
field_scale( new QLineEdit() ),
urlEdit(new QLineEdit()),
formatBox(new QComboBox()),
request_btn( new QPushButton( tr( "Request map" ) ) )
{
/*
* Do not rescale left dock panel's widgets and contents.
*/
QSizePolicy size_policy;
size_policy.setVerticalPolicy( QSizePolicy::Fixed );
size_policy.setHorizontalPolicy( QSizePolicy::Fixed );
setSizePolicy( size_policy );
/*
* Placing console parameters into fields, if they are present
* If they are not present, use placeholders with input hints.
*/
if ( width != 0 )
field_width->setText( QString::number( width, 10 ) );
if ( height != 0 )
field_height->setText( QString::number( height, 10 ) );
if ( x_pos != -999 )
field_x_pos->setText( QString::number( x_pos, 'f', 6 ) );
else
field_x_pos->setPlaceholderText( QString( "[-180...180]" ) );
if ( y_pos != -999 )
field_y_pos->setText( QString::number( y_pos, 'f', 6 ) );
else
field_y_pos->setPlaceholderText( QString( "[-90...90]" ) );
if ( scale != 0 )
field_scale->setText( QString::number( scale, 10 ) );
else
field_scale->setText( "1000000" );
QStringList acceptedFormats;
acceptedFormats << "bmp" << "png" << "jpg";
formatBox->addItems(acceptedFormats);
int idx = formatBox->findText(image_format);
if (idx >= 0)
formatBox->setCurrentIndex(idx);
urlEdit->setText( server_url );
/*
* Acceptable longtitude range: [ -180 ... 180 ].
*/
longtitude_validator = new QDoubleValidator();
longtitude_validator->setRange( -180.0, 180.0 );
longtitude_validator->setDecimals( 8 );
longtitude_validator->setNotation( QDoubleValidator::StandardNotation );
field_x_pos->setValidator( longtitude_validator );
/*
* Acceptable latitude range: [ -90 ... 90 ].
*/
latitude_validator = new QDoubleValidator();
latitude_validator->setRange( -90.0, 90.0 );
latitude_validator->setDecimals( 8 );
latitude_validator->setNotation( QDoubleValidator::StandardNotation );
field_y_pos->setValidator( latitude_validator );
/*
* Acceptable width and height of resulting image in pixels: positive values.
*/
QIntValidator *pixel_validator = new QIntValidator();
pixel_validator->setBottom( 1 );
field_width->setValidator( pixel_validator );
field_height->setValidator( pixel_validator );
/*
* Setting up the left dock panel's vertical layout order.
*/
QVBoxLayout *vlayout = new QVBoxLayout();
QHBoxLayout *hlayout = new QHBoxLayout();
vlayout->addWidget( new QLabel( tr( "Image width (px):" ) ) );
vlayout->addWidget( field_width );
vlayout->addWidget( new QLabel( tr( "Image height (px):" ) ) );
vlayout->addWidget( field_height );
vlayout->addWidget( new QLabel( tr( "Longtitude:" ) ) );
vlayout->addWidget( field_x_pos );
vlayout->addWidget( new QLabel( tr( "Latitude:" ) ) );
vlayout->addWidget( field_y_pos );
vlayout->addWidget( new QLabel( tr( "Scale:" ) ) );
hlayout->addWidget( new QLabel( "1:" ) );
hlayout->addWidget( field_scale );
vlayout->addLayout( hlayout );
vlayout->addWidget( new QLabel( tr( "URL:" ) ) );
vlayout->addWidget( urlEdit );
vlayout->addWidget( new QLabel( tr( "Format:" ) ) );
vlayout->addWidget(formatBox);
vlayout->addWidget( request_btn );
setLayout( vlayout );
connect( request_btn, SIGNAL( released() ),
this, SLOT( request_btn_handler() ) );
/*
* Go to the next input field if user pressed Enter in the previous one
*/
connect( field_width, SIGNAL( returnPressed() ),
this, SLOT( go_to_field_height() ) );
connect( field_height, SIGNAL( returnPressed() ),
this, SLOT( go_to_field_x_pos() ) );
connect( field_x_pos, SIGNAL( returnPressed() ),
this, SLOT( go_to_field_y_pos() ) );
connect( field_y_pos, SIGNAL( returnPressed() ),
this, SLOT( go_to_field_scale() ) );
connect( field_scale, SIGNAL( returnPressed() ),
this, SLOT( go_to_request_btn() ) );
request_btn->setDefault( true );
/*
* Trying to imitate click
*/
if ( _consoleMode ) {
request_btn_handler();
}
}
void MainWidget::start_processing_request()
{
/*
* Latitudes and longtitudes may have "Intermediate" validation state,
* which means that if 180 is the greatest value that is acceptable,
* user can't type in 1800, but he can type in 184, which is out of range.
* This check prohibits invalid input.
*
* Width and height validation is omitted due to the fact that only
* positive numbers are allowed.
*
* See: https://doc.qt.io/qt-5/qintvalidator.html#validate
*/
int cursor = 0;
QString temp_x_pos = field_x_pos->text();
QString temp_y_pos = field_y_pos->text();
if ( longtitude_validator->validate( temp_x_pos, cursor ) != QValidator::Acceptable )
{
field_x_pos->clear();
return;
}
if ( latitude_validator->validate( temp_y_pos, cursor ) != QValidator::Acceptable )
{
field_y_pos->clear();
return;
}
/*
* Request map rendering from server and wait for reply.
*/
process_request();
}
void MainWidget::process_request()
{
double lon = field_x_pos->text().toDouble();
double lat = field_y_pos->text().toDouble();
uint w = field_width->text().toUInt();
uint h = field_height->text().toUInt();
uint scale = field_scale->text().toUInt();
QString format = formatBox->currentText();
QString url = urlEdit->text();
qDebug() << "Sending request: ";
qDebug() << "X:" << lon
<< "Y:" << lat
<< "Width:" << w
<< "Height:" << h
<< "Scale: 1 :" << scale
<< "Format:" << format;
uint orderId = 0;
char pinCode[128];
int status = gis_map_server_request_map( url.toStdString().c_str(), lat, lon, scale, w, h, format.toStdString().c_str(), &orderId, pinCode );
if ( status != 0 ) {
qDebug() << "Failed to make request" << status;
if ( _consoleMode ) {
exit(1);
}
return;
}
qDebug() << "Client got id:" << orderId;
qDebug() << "Client got pincode:" << pinCode;
status = gis_map_server_get_image( url.toStdString().c_str(), orderId, pinCode, &image );
if ( status != 0 ) {
qDebug() << "Failed to get image" << status;
if ( _consoleMode ) {
exit(1);
}
return;
}
qDebug() << "Got image";
if ( _consoleMode ) {
if ( image.save( _outputImageName ) )
qDebug() << "Image was saved to current directory as " << _outputImageName;
else
qDebug() << "Failed to save image as " << _outputImageName;
exit(0);
}
emit request_completed( image );
}
void MainWidget::set_UI_active( bool state )
{
field_x_pos->setEnabled( state );
field_y_pos->setEnabled( state );
field_width->setEnabled( state );
field_height->setEnabled( state );
field_scale->setEnabled( state );
}
void MainWidget::request_btn_handler()
{
set_UI_active( false );
start_processing_request();
set_UI_active( true );
}

72
src/gis-client-render/mainwidget.h Обычный файл
Просмотреть файл

@ -0,0 +1,72 @@
/*
* (c) 2011-2022, SWD Embedded Systems Limited, http://www.kpda.ru
*/
#ifndef MAINWIDGET_H
#define MAINWIDGET_H
#include <QComboBox>
#include <QLineEdit>
#include <QPushButton>
#include <QValidator>
#include <QWidget>
class MainWidget : public QWidget
{
Q_OBJECT
public:
explicit MainWidget( double x_pos,
double y_pos,
uint32_t width,
uint32_t height,
uint32_t scale,
const char *server_url,
const char *image_format,
const char *outputImageName,
bool consoleMode );
QImage image;
private:
const bool _consoleMode;
const char *_outputImageName;
const char *_server_url;
const char *_image_format;
void process_request();
void set_UI_active( bool state );
bool request_btn_state;
QDoubleValidator *longtitude_validator;
QDoubleValidator *latitude_validator;
QLineEdit *field_x_pos;
QLineEdit *field_y_pos;
QLineEdit *field_width;
QLineEdit *field_height;
QLineEdit *field_scale;
QLineEdit *urlEdit;
QComboBox *formatBox;
QPushButton *request_btn;
signals:
void request_completed( const QImage &img );
void request_failed();
public slots:
void start_processing_request();
void request_btn_handler();
/*
* Go to the next input field if user pressed Enter in the previous one
*/
void go_to_field_height() { field_height->setFocus( Qt::OtherFocusReason ); }
void go_to_field_x_pos() { field_x_pos->setFocus( Qt::OtherFocusReason ); }
void go_to_field_y_pos() { field_y_pos->setFocus( Qt::OtherFocusReason ); }
void go_to_field_scale() { field_scale->setFocus( Qt::OtherFocusReason ); }
void go_to_request_btn() { request_btn->setFocus( Qt::OtherFocusReason ); }
};
#endif // MAINWIDGET_H

63
src/gis-client-render/mainwindow.cpp Обычный файл
Просмотреть файл

@ -0,0 +1,63 @@
/*
* (c) 2011-2022, SWD Embedded Systems Limited, http://www.kpda.ru
*/
#include "mainwindow.h"
#include "mainwidget.h"
MainWindow::MainWindow( double x_pos,
double y_pos,
uint32_t width,
uint32_t height,
uint32_t scale,
const char *server_url,
const char *image_format,
const char *outputImageName,
bool consoleMode ) : QMainWindow()
{
/*
* Left dock menu panel widget with request parameters
*/
QDockWidget *dock_widget = new QDockWidget( tr( "Request parameters" ), this );
dock_widget->setAllowedAreas( Qt::LeftDockWidgetArea );
dock_widget->setFeatures( QDockWidget::NoDockWidgetFeatures );
MainWidget *menu = new MainWidget( x_pos,
y_pos,
width,
height,
scale,
server_url,
image_format,
outputImageName,
consoleMode );
dock_widget->setWidget( menu );
addDockWidget( Qt::LeftDockWidgetArea, dock_widget );
/*
* Resulting image viewport widget
*/
viewport = new ImageViewport();
viewport->show();
connect( menu, SIGNAL( request_completed( const QImage&) ),
viewport, SLOT( show_map_image( const QImage&) ) );
connect( menu, SIGNAL( request_failed() ),
viewport, SLOT( clean_map_image() ) );
setMinimumSize( 700, 400 );
setWindowTitle( tr( "Cartographic client" ) );
setCentralWidget( viewport );
}
/*
* Reimplementation of Qt's resizeEvent() for image fitting
* in the viewport after each change of the window's size.
*/
void MainWindow::resizeEvent( QResizeEvent *event )
{
Q_UNUSED( event );
viewport->fit_image();
}

35
src/gis-client-render/mainwindow.h Обычный файл
Просмотреть файл

@ -0,0 +1,35 @@
/*
* (c) 2011-2022, SWD Embedded Systems Limited, http://www.kpda.ru
*/
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QDockWidget>
#include <QMainWindow>
#include <QWheelEvent>
#include "image_viewport.h"
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow( double x_pos,
double y_pos,
uint32_t width,
uint32_t height,
uint32_t scale,
const char *server_url,
const char *image_format,
const char *outputImageName,
bool consoleMode );
private:
ImageViewport *viewport;
protected:
virtual void resizeEvent( QResizeEvent *event );
};
#endif // MAINWINDOW_H

156
src/gis-client-render/mapsender.cpp Обычный файл
Просмотреть файл

@ -0,0 +1,156 @@
/*
* (c) 2011-2022, SWD Embedded Systems Limited, http://www.kpda.ru
*/
#include "mapsender.h"
#include <QApplication>
#include <QBuffer>
#include <QDateTime>
#include <QDebug>
#include <QImage>
#include "gismapserver.h"
int MapSender::sendRequest( double lat,
double lon,
int scale,
int w,
int h,
const char* format,
uint *orderId,
char *pinCode )
{
QString requestUrl = _url + "/?";
requestUrl += "&lat=" + QString::number(lat);
requestUrl += "&lon=" + QString::number(lon);
requestUrl += "&scale=" + QString::number(scale);
requestUrl += "&w=" + QString::number(w);
requestUrl += "&h=" + QString::number(h);
requestUrl += "&format=" + QString(format);
QUrl url( requestUrl );
QNetworkRequest request(url);
request.setRawHeader( QByteArray("agent"), QByteArray("gis") );
QNetworkReply *reply = manager->get(request);
waitForReplyFinished( reply, _timeout_ms );
QVariant statusCodeAttribute = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
bool ok = false;
GisHttpCodes statusCode = static_cast<GisHttpCodes>(statusCodeAttribute.toInt(&ok));
if ( !ok || statusCode != GisHttpCodes::Ok ) {
qDebug() << gis_map_server_get_error_message( statusCode );
return EFAULT;
}
QString orderId_tag = "orderId=";
QString pinCode_tag = "pincode=";
QByteArray rawData = reply->readAll();
QList<QByteArray> rawLines = rawData.split(',');
QString orderStr( rawLines[0] );
if (orderStr.contains( orderId_tag )) {
orderStr.remove(orderId_tag);
*orderId = orderStr.toUInt( &ok );
if ( !ok ) {
qDebug() << "Invalid <orderId>";
return EFAULT;
}
}
QString pinCodeStr( rawLines[1] );
if (pinCodeStr.contains( pinCode_tag )) {
pinCodeStr.remove(pinCode_tag);
pinCodeStr.remove( " " );
strcpy( pinCode, pinCodeStr.toStdString().c_str() );
}
return 0;
}
int MapSender::getImageWithWaiting( char *pinCode, QImage *image )
{
uint waitMs = 100;
QDateTime start = QDateTime::currentDateTimeUtc();
QDateTime prevRequestTime = QDateTime::currentDateTimeUtc();
QDateTime currentRequestTime = prevRequestTime;
int status = getImage( pinCode, image );
while ( status == EAGAIN ) {
qApp->processEvents( QEventLoop::AllEvents, waitMs );
currentRequestTime = QDateTime::currentDateTimeUtc();
qint64 requestdeltaMs = prevRequestTime.msecsTo( currentRequestTime );
if ( requestdeltaMs > 500 ) {
prevRequestTime = currentRequestTime;
status = getImage( pinCode, image );
}
QDateTime current = QDateTime::currentDateTimeUtc();
auto totalDeltaMs = start.msecsTo( current );
if ( totalDeltaMs > _timeout_ms )
return ETIMEDOUT;
}
return status;
}
int MapSender::getImage( char *pinCode, QImage *image )
{
QString requestUrl = _url + "/?&orderId=" + QString::number(_orderId) + "&pincode=" + QString(pinCode);
QUrl url( requestUrl );
QNetworkRequest request(url);
QNetworkReply *reply = manager->get(request);
waitForReplyFinished( reply, _timeout_ms );
QVariant statusCodeAttribute = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
bool ok = false;
int statusCode = statusCodeAttribute.toInt(&ok);
if ( !ok || statusCode != GisHttpCodes::Ok ) {
if ( statusCode == GisHttpCodes::IsProcessing ) {
return EAGAIN;
}
qDebug() << "statusCode" << statusCode;
return EFAULT;
}
if ( !reply->isFinished() )
return EFAULT;
QVariant format_hdr = reply->header( QNetworkRequest::ContentTypeHeader );
QString format_str = format_hdr.toString();
QString image_format = format_str.remove( "image/" );
QByteArray ba = reply->readAll();
image->loadFromData( ba, image_format.toLocal8Bit().data() );
return 0;
}
void MapSender::waitForReplyFinished( QNetworkReply *reply, uint timeout_ms )
{
uint waitMs = 100;
bool isTimeoutEnabled = false;
if ( timeout_ms > 0 )
isTimeoutEnabled = true;
QDateTime start = QDateTime::currentDateTimeUtc();
qint64 totalDeltaMs = 0;
while ( !reply->isFinished() ) {
qApp->processEvents( QEventLoop::AllEvents, waitMs );
if ( isTimeoutEnabled ) {
QDateTime current = QDateTime::currentDateTimeUtc();
totalDeltaMs = start.msecsTo( current );
if ( totalDeltaMs > timeout_ms )
{
qDebug() << "Timeout" << totalDeltaMs;
break;
}
}
}
}

37
src/gis-client-render/mapsender.h Обычный файл
Просмотреть файл

@ -0,0 +1,37 @@
/*
* (c) 2011-2022, SWD Embedded Systems Limited, http://www.kpda.ru
*/
#ifndef MAPSENDER_H
#define MAPSENDER_H
#include <QNetworkReply>
#include <QNetworkAccessManager>
class MapSender : public QObject
{
Q_OBJECT
public:
explicit MapSender( const QString &server_address, const int userId, const uint timeout_ms ) :
_url( server_address ),
_orderId( userId ),
_timeout_ms( timeout_ms )
{
manager = new QNetworkAccessManager();
}
//Client calls
int sendRequest(double lat, double lon, int scale, int w, int h, const char* format, uint* userId , char* pinCode);
int getImageWithWaiting( char *pinCode, QImage *image );
private:
int getImage( char *pinCode, QImage *image );
void waitForReplyFinished( QNetworkReply *reply, uint timeout_ms );
const QString _url;
const int _orderId;
const uint _timeout_ms;
QNetworkAccessManager *manager;
};
#endif // MAPSENDER_H

8
src/gis-client-render/nto/Makefile Исполняемый файл
Просмотреть файл

@ -0,0 +1,8 @@
LIST=CPU
ifndef QRECURSE
QRECURSE=recurse.mk
ifdef QCONFIG
QRDIR=$(dir $(QCONFIG))
endif
endif
include $(QRDIR)$(QRECURSE)

8
src/gis-client-render/nto/arm/Makefile Исполняемый файл
Просмотреть файл

@ -0,0 +1,8 @@
LIST=VARIANT
ifndef QRECURSE
QRECURSE=recurse.mk
ifdef QCONFIG
QRDIR=$(dir $(QCONFIG))
endif
endif
include $(QRDIR)$(QRECURSE)

1
src/gis-client-render/nto/arm/o.le.v7/Makefile Исполняемый файл
Просмотреть файл

@ -0,0 +1 @@
include ../../../common.mk

8
src/gis-client-render/nto/e2k/Makefile Исполняемый файл
Просмотреть файл

@ -0,0 +1,8 @@
LIST=VARIANT
ifndef QRECURSE
QRECURSE=recurse.mk
ifdef QCONFIG
QRDIR=$(dir $(QCONFIG))
endif
endif
include $(QRDIR)$(QRECURSE)

1
src/gis-client-render/nto/e2k/o.le/Makefile Исполняемый файл
Просмотреть файл

@ -0,0 +1 @@
include ../../../common.mk

8
src/gis-client-render/nto/mips/Makefile Исполняемый файл
Просмотреть файл

@ -0,0 +1,8 @@
LIST=VARIANT
ifndef QRECURSE
QRECURSE=recurse.mk
ifdef QCONFIG
QRDIR=$(dir $(QCONFIG))
endif
endif
include $(QRDIR)$(QRECURSE)

1
src/gis-client-render/nto/mips/o.be/Makefile Исполняемый файл
Просмотреть файл

@ -0,0 +1 @@
include ../../../common.mk

8
src/gis-client-render/nto/ppc/Makefile Исполняемый файл
Просмотреть файл

@ -0,0 +1,8 @@
LIST=VARIANT
ifndef QRECURSE
QRECURSE=recurse.mk
ifdef QCONFIG
QRDIR=$(dir $(QCONFIG))
endif
endif
include $(QRDIR)$(QRECURSE)

1
src/gis-client-render/nto/ppc/o.be.spe/Makefile Исполняемый файл
Просмотреть файл

@ -0,0 +1 @@
include ../../../common.mk

1
src/gis-client-render/nto/ppc/o.be/Makefile Исполняемый файл
Просмотреть файл

@ -0,0 +1 @@
include ../../../common.mk

8
src/gis-client-render/nto/x86/Makefile Исполняемый файл
Просмотреть файл

@ -0,0 +1,8 @@
LIST=VARIANT
ifndef QRECURSE
QRECURSE=recurse.mk
ifdef QCONFIG
QRDIR=$(dir $(QCONFIG))
endif
endif
include $(QRDIR)$(QRECURSE)

1
src/gis-client-render/nto/x86/o/Makefile Исполняемый файл
Просмотреть файл

@ -0,0 +1 @@
include ../../../common.mk

5
src/gis-client-render/translations.qrc Обычный файл
Просмотреть файл

@ -0,0 +1,5 @@
<RCC>
<qresource prefix="/">
<file>translations/gis-client-render_ru.qm</file>
</qresource>
</RCC>

Просмотреть файл

@ -0,0 +1,56 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.0" language="ru_RU">
<context>
<name>MainWidget</name>
<message>
<location filename="../mainwidget.cpp" line="12"/>
<source>Longtitude:</source>
<translation>Долгота:</translation>
</message>
<message>
<location filename="../mainwidget.cpp" line="13"/>
<source>Latitude:</source>
<translation>Широта:</translation>
</message>
<message>
<location filename="../mainwidget.cpp" line="14"/>
<source>Image width (px):</source>
<translation>Ширина изображения в пикс.</translation>
</message>
<message>
<location filename="../mainwidget.cpp" line="15"/>
<source>Image height (px):</source>
<translation>Высота изображения в пикс.</translation>
</message>
<message>
<location filename="../mainwidget.cpp" line="16"/>
<source>Scale:</source>
<translation>Масштаб:</translation>
</message>
<message>
<location filename="../mainwidget.cpp" line="23"/>
<location filename="../mainwidget.cpp" line="204"/>
<source>Request map</source>
<translation>Выполнить запрос</translation>
</message>
<message>
<location filename="../mainwidget.cpp" line="206"/>
<source>Abort request</source>
<translation>Прервать запрос</translation>
</message>
</context>
<context>
<name>MainWindow</name>
<message>
<location filename="../mainwindow.cpp" line="31"/>
<source>Cartographic client</source>
<translation>Картографический клиент</translation>
</message>
<message>
<location filename="../mainwindow.cpp" line="12"/>
<source>Request parameters</source>
<translation>Параметры запроса</translation>
</message>
</context>
</TS>

2
src/gis-map-server/Makefile Обычный файл
Просмотреть файл

@ -0,0 +1,2 @@
LIST=OS
include recurse.mk

6
src/gis-map-server/README.md Обычный файл
Просмотреть файл

@ -0,0 +1,6 @@
## gis-map-server - карт-сервер, предназначенный для работы с ПК ЦКИ
<br/>
<br/>
![GIS Map Server IPC](docs/gis-map-server_IPC.drawio.png)

15
src/gis-map-server/START.sh Исполняемый файл
Просмотреть файл

@ -0,0 +1,15 @@
#!/bin/sh
if [ -z "$GIS_ROOT" ]; then
export GIS_ROOT=/opt/gis/
fi
export LD_LIBRARY_PATH=$GIS_ROOT/lib:$LD_LIBRARY_PATH
export PATH=$GIS_ROOT/bin:$GIS_ROOT/sbin:$PATH
export GIS_DEBUG_LEVEL=2
export GIS_DISABLE_VERIFIER=y
slay -f gis-core
gis-core -dsxf-local,sync=soft -ds57-local,sync=soft &
waitfor /dev/gis_core 300
echo 'Starting server'
$GIS_ROOT/data/resources/gis-map-server/server.py $GIS_ROOT/data/config/gis-map-server.conf

20
src/gis-map-server/common.mk Обычный файл
Просмотреть файл

@ -0,0 +1,20 @@
ifndef QCONFIG
QCONFIG=qconfig.mk
endif
include $(QCONFIG)
include $(MKFILES_ROOT)/qmacros.mk
NAME=$(notdir ${PROJECT_ROOT})
INSTALLDIR=/opt/gis/sbin/
RESOURCESDIR=/opt/gis/data/resources/${NAME}
LOGDIR=/opt/gis/data/config
PRE_INSTALL=$(CP_HOST) ${PROJECT_ROOT}/START.sh ${INSTALL_ROOT_nto}/${CPUVARDIR}/${INSTALLDIR}/${NAME}; $(CP_HOST) ${PROJECT_ROOT}/*.py ${INSTALL_ROOT_nto}/${CPUVARDIR}/${RESOURCESDIR}/
POST_INSTALL=$(CP_HOST) -R ${PROJECT_ROOT}/html ${INSTALL_ROOT_nto}/${CPUVARDIR}/${RESOURCESDIR}; $(CP_HOST) ${PROJECT_ROOT}/$(NAME).conf ${INSTALL_ROOT_nto}/${CPUVARDIR}/${LOGDIR}/$(NAME).conf
ALL_DEPENDENCIES=Makefile
include $(MKFILES_ROOT)/qtargets.mk

Просмотреть файл

@ -0,0 +1 @@
<mxfile host="app.diagrams.net" modified="2021-12-21T13:21:52.932Z" agent="5.0 (X11)" etag="9BBCW-6gea-xDeGxZfHn" version="16.0.0" type="device"><diagram id="b_8fcSy7Od5qwKq7muho" name="Page-1">7Vhbb5swFP41PG7iEiA8hqTdHjatWnd9qhxwwZvBkXGadL9+tjHGBtpcVLXVNAkhczgc4+/77HNsJ1hW+3cUbMqPJIfY8d187wQrx/e9me874nLz+9Yyd5WhoChXTr3hGv2Byugq6xblsLEcGSGYoY1tzEhdw4xZNkAp2dlutwTbvW5AAUeG6wzgsfU7ylmpRuHHvf09REXZ9exFSfumAp2zGklTgpzsDFNw4QRLSghrW9V+CbEAr8Ol/e7ygbf6xyis2TEffLnJPq9XYEHd5nLbXCf4J/rxRkW5A3irBuysXCdZiHuayvtc3kN5Xzkrz5n78p6qYbH7Dqs7SBni0H0Aa4ivSIMYIjV/tSaMkcoJ0s5hgVEhXjCy4daSVZg/eLzJMdqIYNW+EHJ6uwYNyt5icA8pzG+oJDiV4uL6CFKyZRjVcKm5F0Y1It4V3D8IlacJ4MqFpIKM3nOXfSfS9gulWS8O2+ddrwDPVT6lwX6kbECJrtCRe154Q1FzAk3+mCY/whIMdMebhWgazLmSIbdji1su5D3o7Npn1nHM23HHNG97sr0wFJB0XfIRmL1O/0h62XWiw4Tqd9QHazoMMQz8r8orenX6Cib0NUAf1vlCrKf8qSY1FECBpoS5gtZAWdivAGOQ1tLiu4HAnlHyW6+hHJ2Ukm2dywA9rjAfLccHUTVQCydA62wUYsDQnR1+CknVwxVBvGNNmj+zSQviARkN2dIMqq/MdfhAoCGpDNACslEcyase9flUz/5TfYjqaER1eB7Vw0BR+KxUh4eptonZlYjB6w3IxNsdXyLHbGsVjEneQIr4L0MqIqC6UI5PsGCOEnK3OBp6CKYWTF1GPvmKGR2bkReelfz6WsrMyzy3usfnxemEO7crNN2nmedD1Vub4Y9Lu71EvMMSGaviKehPYpv+rvo286X/nPkyHrMvqA7t8qqlQ1OTGqVWouh4fWDrvKbA1vuXFwN7fuxUS5b2VkW3W/FH1qQ4aW6ZVbJnlLehsR+K7TmnaY+Najsy7POzC+GXF8lsFjzbjPzaQPpp/UtU4L6LRe1vzDhvBLq99LXjwhycku/8IbVPA2pQKUi/AYrAmgMs7Y/KMTGQUfI1uOGYsikClgQT2hdUtwjjgQmoXUvGOYB0YjtToTwX3UwyTrj3LZY1W8n9YP1Eq0EyXawaPM+n8u7pNPNHg+lTjjDcw2WOUdFmGDR81yeJAZSNzQZ155WtvGdZHz72z61fW/8dquBebMfjDTJBeO6OJxoEcp93y+NNnHK9doWERyrEf0mFDA8yguHyfqxC4mAQaFi3n60Q/tgfsbbu/UF1cPEX</diagram></mxfile>

Двоичные данные
src/gis-map-server/docs/architecture.drawio.png Обычный файл

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 15 KiB

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Двоичные данные
src/gis-map-server/docs/gis-map-server_IPC.drawio.png Обычный файл

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 90 KiB

Двоичные данные
src/gis-map-server/docs/web_interface1.png Обычный файл

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 216 KiB

6
src/gis-map-server/gis-map-server.conf Обычный файл
Просмотреть файл

@ -0,0 +1,6 @@
SERVER_ADDRESS=localhost
SERVER_PORT=8000
SLOTS_NUMBER=2
STORAGE_MAX_SIZE=6400000
HTML_PAGES_PATH=./data/resources/gis-map-server/html/
GIS_SHID=770

14
src/gis-map-server/html/order_request.html Обычный файл
Просмотреть файл

@ -0,0 +1,14 @@
<html>
<head>
<title></title>
<meta content="">
<style></style>
</head>
<body>
<center>
<h2>Order is accepted</h2>
<p>Order id = ORDERID, pincode = PIN_CODE</p>
<a href='http://ADDRESS:PORT/?orderId=ORDERID&pincode=PIN_CODE'> Click here to obtain your order </a>
</center>
</body>
</html>

165
src/gis-map-server/html/start_page.html Обычный файл
Просмотреть файл

@ -0,0 +1,165 @@
<html>
<head>
<title></title>
<meta content="">
<style>
.params_frame {
width: 200px;
padding: 10px;
float: left;
border: 1px solid black;
margin: 7px;
}
.results_frame {
width: calc(100% - 288px);
height: calc(100vh - 58px );
float: left;
border: 1px solid black;
margin: 7px;
overflow: scroll;
}
</style>
<script>
function new_req() {
var lat = document.getElementById("lat").value;
var lon = document.getElementById("lon").value;
var lon = document.getElementById("lon").value;
var scale = document.getElementById("scale").value;
var h = document.getElementById("h").value;
var w = document.getElementById("w").value;
var format = document.getElementById("format").value;
document.getElementById('but').disabled = true;
if (document.contains(document.getElementById("frame_id"))) {
document.getElementById("frame_id").remove();
}
const iframe = document.createElement('iframe');
iframe.id = "frame_id";
iframe.scrolling = "no";
iframe.style = "border: 0px;"
document.getElementById('results').appendChild(iframe);
res = send_req(lat, lon, scale, w, h, format);
if (res[2] == '200')
{
show_content('Processing...')
counter = 0
var interval_poll = window.setInterval(function()
{
answer = get_ord(res[0], res[1])
if (answer[1] == 200)
{
clearInterval(interval_poll);
iframe.height = h;
iframe.width = w;
iframe.src = 'http://ADDRESS:PORT/?orderId=' + res[0] + '&pincode=' + res[1];
document.getElementById('but').disabled = false;
}
else if (answer[1] == 202)
{
show_content('Processing... ' + answer[1])
}
else
{
clearInterval(interval_poll);
iframe.srcdoc = answer[2];
document.getElementById('but').disabled = false;
}
}, 100);
}
else
{
show_content('<p> <font color="red"> Error code obtained from server (' + res[2] + ') </font> </p>')
document.getElementById('but').disabled = false;
}
}
function show_content(cont){
document.getElementById('frame_id').src = "data:text/html;charset=utf-8," + escape(cont);
}
function send_req(lat, lon, scale, w, h, format) {
const url = 'http://ADDRESS:PORT/?lat='+ lat + '&lon=' + lon + '&scale=' + scale + '&w=' + w +'&h=' + h + '&format=' + format
res = http_get(url)
id = res[0].match('Order id = (.*?),')
pincode = res[0].match('pincode = (.*?)<')
if (id != null && pincode != null)
{
id = id[1]
pincode = pincode[1]
}
return [id, pincode, res[1]];
}
function get_ord(id, pincode) {
const url = 'http://ADDRESS:PORT/?orderId=' + id + '&pincode=' + pincode
return http_get(url);
}
function http_get(url)
{
var xhr = new XMLHttpRequest();
xhr.open( "GET", url, false);
xhr.send( null );
return [xhr.responseText, xhr.status, xhr.response]
}
</script>
</head>
<body>
<div class="params_frame">
<form action="">
<table>
<tr>
<td>
<label for="lat">Latitude</label>
<td>
<input name="lat" id="lat" size="7" value="55.4798">
</tr>
<tr>
<td>
<label for="lon">Longitude</label>
<td>
<input name="lon" id="lon" size="7" value="37.754">
</tr>
<tr>
<td>
<label for="scale">Scale</label>
<td>
<input name="scale" id="scale" size="7" value="10700">
</tr>
<tr>
<td>
<label for="w">Width</label>
<td>
<input name="w" id="w" size="7" value="1000">
</tr>
<tr>
<td>
<label for="h">Height</label>
<td>
<input name="h" id="h" size="7" value="1000">
</tr>
<tr>
<td>
<label for="format">Format</label>
<td>
<input name="format" id="format" size="7" value="png">
</tr>
</table>
<BR>
<div>
<center> <input type="button" value="Send request" id="but" onclick="new_req()"> </center>
</div>
</form>
</div>
<div class="results_frame" id="results">
</div>
</body>
</html>

324
src/gis-map-server/net_interface.py Обычный файл
Просмотреть файл

@ -0,0 +1,324 @@
###############################################################################
# (c) 2011-2022, SWD Embedded Systems Limited, http://www.kpda.ru
###############################################################################
#!/usr/bin/python3 -u
import http.server, ssl
import logging
import sys
import os
import re
from enum import Enum, unique
from utils import Request_Status, get_log_path, get_status_desc
@unique
class Page_Type(Enum):
START = 0
ORDER_REQUEST = 1
BAD_REQUEST = 2
class Handler(http.server.BaseHTTPRequestHandler):
"""
A class for http-requests handling. Instance's lifetime is limited by the time of request processing.
Attributes:
__________
pipe_conn : multiprocessing.connection.Connection
Pipe connection instance for interacting with Scheduler.
buffer : Storage_Class instance
Buffer is used to store data from POST-requests obtained by Renderer.
html_path : str
The path to a folder with html pages that are used to responde clients.
Methods:
________
get_html_content(page_type)
Obtains the content of html page of specified type and returns it as a string.
do_POST()
Handles POST requests.
do_GET()
Handles GET requests.
bad_request(code, exc="")
Inserts a error message obtained by code and prints an html-page into wfile. Also supports printing an exception string.
clear_pipe()
Obtains all possible data from Pipe connection if it exists.
"""
def __init__(self, pipe_conn, buffer, html_path, *args):
"""
Parameters:
__________
pipe_conn : multiprocessing.connection.Connection
Pipe connection instance for interacting with Scheduler.
buffer : Storage_Class instance
Buffer is used to store data from POST-requests obtained by Renderer.
html_path : str
The path to a folder with html pages that are used to responde clients.
"""
self.pipe_conn = pipe_conn
self.buffer = buffer
self.html_path = html_path
http.server.BaseHTTPRequestHandler.__init__(self, *args)
def get_html_content(self, page_type):
"""Obtains the content of html page of specified type and returns it as a string.
Parameters:
__________
page_type : Page_Type Enum instance
Type of page which should be read.
Returns
-------
str
Content of html page
"""
if page_type == Page_Type.START.value:
with open(self.html_path + '/start_page.html') as f:
content = f.read()
content = content.replace('ADDRESS', str(self.server.server_name))
content = content.replace('PORT', str(self.server.server_port))
return content
elif page_type == Page_Type.ORDER_REQUEST.value:
with open(self.html_path + '/order_request.html') as f:
content = f.read()
content = content.replace('ADDRESS', str(self.server.server_name))
content = content.replace('PORT', str(self.server.server_port))
return content
else:
return 'BadPage'
def do_POST(self):
""" Handles POST requests. """
try:
# trying to get payload
logging.debug('Got POST-request')
length = int(self.headers['Content-Length'])
logging.debug('Got length')
orderId = int(self.headers['orderId'])
logging.debug('Got orderId')
img_format = self.headers['Content-Type']
logging.debug('Got img_format')
payload = self.rfile.read(length)
logging.debug('Got payload')
status_code, deleted_ids = self.buffer.push(orderId, payload, img_format, length)
#sending information to scheduler
self.clear_pipe()
self.pipe_conn.send((2, deleted_ids))
if self.pipe_conn.poll(1):
logging.debug('net_interface: got answer from scheduler')
answer = self.pipe_conn.recv()
if answer == False:
self.bad_request(Request_Status.REQUEST_FAILED.value, "Scheduler could not delete previous ids from table")
else:
self.bad_request(Request_Status.TIMEOUT.value, "Scheduler did not responde")
if status_code == Request_Status.READY.value:
logging.debug('Push success')
self.send_response(Request_Status.READY.value, "Got payload")
self.end_headers()
self.wfile.write('Accepted'.encode())
else:
logging.debug('Error while pushing {status_code}')
self.bad_request(status_code, "Id is busy")
except Exception as exc:
try:
self.bad_request(Request_Status.INVALID_PARAM.value, exc)
except Exception as exc:
logging.debug(f"Bad connection with client {exc}")
def bad_request(self, code, exc=""):
""" Prints an html-page with error code into wfile and inserts there an error message obtained by code. Also supports printing an exception string.
Parameters:
__________
code : Request_Status Enum instance
An error code to print.A string of thrown exception to print.
exc : str
A string of thrown exception to print.
"""
self.send_response(code)
self.send_header("Content-type", "text/html")
self.send_header("Access-Control-Allow-Origin", "*")
self.end_headers()
self.wfile.write('<html><head><meta charset="utf-8">'.encode())
self.wfile.write('<title>Bad request</title></head>'.encode())
self.wfile.write(f'<body><p>Bad request: {code}:{get_status_desc(code)} {exc}</p></body></html>'.encode())
logging.debug('requested')
def clear_pipe(self):
""" Obtains all possible data from Pipe connection if it exists. """
while True:
logging.debug('clear pipe start')
if self.pipe_conn.poll():
self.pipe_conn.recv()
continue
logging.debug('clear pipe finish')
break
def do_GET(self):
""" Handles POST requests. """
try:
#analyzing agent
try:
if 'gis' in dict(self.headers)['agent']:
logging.debug('Agent: GIS')
gis_agent = True
except:
logging.debug('Agent: Unknown')
gis_agent = False
# analyzing of parameters
fields = dict()
param_line = re.sub('/[?]*[&]*', '', self.path)
if '&' in param_line:
for p in param_line.split('&'):
print(p.split('='))
key, val = p.split('=')
fields[key] = val
logging.debug(f"I've got a GET request, fields = {fields} from {self.path}")
if len(fields) == 0:
# start page request
answer = self.get_html_content(Page_Type.START.value)
self.send_response(Request_Status.READY.value)
self.send_header("Content-type", "text/html")
self.send_header("Access-Control-Allow-Origin", "*")
self.end_headers()
self.wfile.write(answer.encode())
elif 'orderId' not in fields:
# first type of GET-request
#sending to scheduler new order
self.clear_pipe()
self.pipe_conn.send((0, fields))
# obtaining id from scheduler
if self.pipe_conn.poll(1):
logging.debug('net_interface: got from scheduler')
container = self.pipe_conn.recv()
logging.debug(f'OBTAINED {container}')
id_pin, is_valid = container
orderId, pincode = id_pin
logging.debug(f'net_interface: got from scheduler {orderId}, {is_valid}, {pincode}')
if is_valid:
self.send_response(Request_Status.READY.value)
logging.debug('net_interface: 0')
self.send_header("Content-type", "text/html")
self.send_header("Access-Control-Allow-Origin", "*")
logging.debug('net_interface: 1')
self.end_headers()
logging.debug('net_interface: 2')
if gis_agent:
logging.debug('net_interface: 3')
self.wfile.write(f'orderId={orderId}, pincode={pincode}'.encode())
else:
logging.debug('net_interface: 4')
answer = self.get_html_content(Page_Type.ORDER_REQUEST.value)
answer = answer.replace('ORDERID', str(orderId))
answer = answer.replace('PIN_CODE', pincode)
self.wfile.write(answer.encode())
else:
# internal error, bad request params
self.bad_request(Request_Status.INVALID_PARAM.value)
else:
# timeout error
self.bad_request(Request_Status.TIMEOUT.value)
else:
# second type of GET-request
if 'pincode' not in fields:
self.bad_request(Request_Status.INVALID_PARAM.value)
else:
# asking scheduler about status of order
orderId = int(fields['orderId'])
pincode = fields['pincode']
self.clear_pipe()
self.pipe_conn.send((1, (orderId, pincode)))
# obtaining id from scheduler
if self.pipe_conn.poll(2):
status = self.pipe_conn.recv()
if status == Request_Status.READY.value:
output_data, img_format = self.buffer.pop_by_id(orderId)
self.send_response(Request_Status.READY.value)
self.send_header("Content-type", img_format)
self.send_header("Access-Control-Allow-Origin", "*")
self.end_headers()
self.wfile.write(output_data)
else:
logging.debug(f'status: {status}')
self.bad_request(status)
else:
self.bad_request(Request_Status.TIMEOUT.value)
except Exception as exc:
logging.debug("Error! {0}".format(exc))
self.bad_request(Request_Status.REQUEST_FAILED.value, exc)
class Net_Interface():
"""
A class to provide net-interface of map-server. For now it start http server.
Attributes:
__________
host : str
IP adress or host name for the net interface module.
port : int
Connection port for the net interface module.
Methods:
________
init_logging(page_type)
Starts logging to the file, obtained by get_log_path.
start_server()
Starts http server using Handler class for requests handling.
"""
def __init__(self, host, port):
"""
Parameters:
__________
host : str
IP adress or host name for the net interface module.
port : int
Connection port for the net interface module.
"""
self.port = port
self.host = host
self.init_logging()
def init_logging(self):
""" Starts logging to the file, obtained by get_log_path. """
log_path = get_log_path()
if log_path == None:
logging.debug("Could not find GIS_ROOT environment variable")
sys.exit(1)
log_path += '/server.log'
logging.basicConfig(format='%(asctime)s %(levelname)-8s %(message)s', filename=log_path, encoding='utf-8', level=logging.DEBUG)
def start_server(self, pipe_conn, buffer, html_path):
""" Starts http server using Handler class for requests handling.
Parameters:
__________
pipe_conn : multiprocessing.connection.Connection
Pipe connection instance for interacting with Scheduler.
buffer : Storage_Class instance
Buffer is used to store data from POST-requests obtained by Renderer.
html_path : str
The path to a folder with html pages that are used to responde clients.
"""
def handler(*args):
Handler(pipe_conn, buffer, html_path, *args)
self.net_server = http.server.HTTPServer((self.host, self.port), handler)
logging.debug('net_server Started')
self.net_server.serve_forever()

8
src/gis-map-server/nto/Makefile Обычный файл
Просмотреть файл

@ -0,0 +1,8 @@
LIST=CPU
ifndef QRECURSE
QRECURSE=recurse.mk
ifdef QCONFIG
QRDIR=$(dir $(QCONFIG))
endif
endif
include $(QRDIR)$(QRECURSE)

8
src/gis-map-server/nto/arm/Makefile Обычный файл
Просмотреть файл

@ -0,0 +1,8 @@
LIST=VARIANT
ifndef QRECURSE
QRECURSE=recurse.mk
ifdef QCONFIG
QRDIR=$(dir $(QCONFIG))
endif
endif
include $(QRDIR)$(QRECURSE)

Просмотреть файл

@ -0,0 +1 @@
include ../../../common.mk

8
src/gis-map-server/nto/e2k/Makefile Обычный файл
Просмотреть файл

@ -0,0 +1,8 @@
LIST=VARIANT
ifndef QRECURSE
QRECURSE=recurse.mk
ifdef QCONFIG
QRDIR=$(dir $(QCONFIG))
endif
endif
include $(QRDIR)$(QRECURSE)

1
src/gis-map-server/nto/e2k/o.le/Makefile Обычный файл
Просмотреть файл

@ -0,0 +1 @@
include ../../../common.mk

8
src/gis-map-server/nto/mips/Makefile Исполняемый файл
Просмотреть файл

@ -0,0 +1,8 @@
LIST=VARIANT
ifndef QRECURSE
QRECURSE=recurse.mk
ifdef QCONFIG
QRDIR=$(dir $(QCONFIG))
endif
endif
include $(QRDIR)$(QRECURSE)

1
src/gis-map-server/nto/mips/o.be/Makefile Исполняемый файл
Просмотреть файл

@ -0,0 +1 @@
include ../../../common.mk

8
src/gis-map-server/nto/ppc/Makefile Обычный файл
Просмотреть файл

@ -0,0 +1,8 @@
LIST=VARIANT
ifndef QRECURSE
QRECURSE=recurse.mk
ifdef QCONFIG
QRDIR=$(dir $(QCONFIG))
endif
endif
include $(QRDIR)$(QRECURSE)

Просмотреть файл

@ -0,0 +1 @@
include ../../../common.mk

1
src/gis-map-server/nto/ppc/o.be/Makefile Обычный файл
Просмотреть файл

@ -0,0 +1 @@
include ../../../common.mk

8
src/gis-map-server/nto/x86/Makefile Обычный файл
Просмотреть файл

@ -0,0 +1,8 @@
LIST=VARIANT
ifndef QRECURSE
QRECURSE=recurse.mk
ifdef QCONFIG
QRDIR=$(dir $(QCONFIG))
endif
endif
include $(QRDIR)$(QRECURSE)

1
src/gis-map-server/nto/x86/o/Makefile Обычный файл
Просмотреть файл

@ -0,0 +1 @@
include ../../../common.mk

317
src/gis-map-server/scheduler.py Обычный файл
Просмотреть файл

@ -0,0 +1,317 @@
###############################################################################
# (c) 2011-2022, SWD Embedded Systems Limited, http://www.kpda.ru
###############################################################################
#!/usr/bin/python3 -u -B
import time
import os
import logging
import subprocess
import string
import random
from utils import Request_Status, get_log_path
class Worker():
"""
A class for organizing the parallel execution of any processes with required number of slots.
Attributes:
__________
slots_num : int
A total number of available slots.
slots : array
An array of processes.
size : int
Current number of executing processes.
Methods:
________
fill_slot(page_type)
Adds new process to the array.
free_slot(num)
Removes a process from slot with index = num.
check_free_slot()
Checks if there is at least one free slot in processes array.
is_busy()
Checks if there is at least one active process.
active_slots()
Returns a list of elements and its indexes from processes array if they are active.
"""
def __init__(self, slots_num):
"""
Parameters:
__________
slots_num : int
A total number of available slots.
"""
self.slots_num = slots_num
self.slots = [0] * self.slots_num
self.size = 0
def fill_slot(self, element):
""" Adds new process to the array.
This function has to be called only after cheking free slot with "check_free_slot".
Parameters:
__________
element : any type
New element to be add into the processes array.
"""
logging.debug(f'{self.slots}')
for i in range(self.slots_num):
if self.slots[i] == 0:
self.slots[i] = element
self.size += 1
return
raise IndexError
def free_slot(self, num):
""" Removes a process from slot with index = num.
Parameters:
__________
num : int
Index of removed slot.
"""
self.slots[num] = 0
self.size -= 1
def check_free_slot(self):
""" Checks if there is at least one free slot in processes array. """
if self.size < self.slots_num:
return True
return False
def is_busy(self):
""" Checks if there is at least one active process. """
if self.size > 0:
return True
return False
def active_slots(self):
""" Returns a list of elements and its indexes from processes array if they are active. """
return [ (self.slots[i], i) for i in range(self.slots_num) if self.slots[i] != 0]
class Scheduler():
"""
A class for organizing orders execution, id and pin codes generation, saving orders return codes, orders validating.
Attributes:
__________
orders : dict
A dictionary of all oders. Key: id, value: [parameters, return codes, pincode]
queue : list
A queue with new orders that have not been executed.
counter : int
Total number of orders.
Methods:
________
validator(params)
Validates parameters of an order.
generate_pincode(dictionary, size)
Generates new pincode of length = size, using characters from dictionary string.
add_order(params)
Adds new order with parameters = params to orders and queue.
check_order(id)
Checks if order with id exists.
start_scheduler(pipe_conn, host, port, slots_num)
Executes scheduler, starts an infinite loop for listening pipe connection = pipe_conn and organizing orders execution.
"""
def __init__(self):
self.orders = dict()
self.cached = dict()
self.queue = list()
self.counter = 0
def validator(self, params):
""" Validates parameters of an order.
Parameters:
__________
params : list
A list of parameters for validating.
"""
params_list = ['lat', 'lon', 'scale', 'w', 'h', 'format']
for par in params_list:
if par not in params:
logging.debug(f'Bad params: no {par} in {params}')
return False
try:
float(params['lon'])
float(params['lat'])
except ValueError:
logging.debug(f'Bad params: lat, lan, w, h or scale is not float in {params}')
return False
if not params['scale'].isdigit() or not params['w'].isdigit() or not params['h'].isdigit():
return False
return True
def generate_pincode(self, dictionary, size):
""" Generates new pincode of length = size, using characters from dictionary string.
Parameters:
__________
dictionary : str
A string with characters that is used as a dictionary for pincode creating.
size : int
A number of characters in picode.
Returns
-------
pincode
Generated pincode
"""
pincode = ''.join(random.choice(dictionary) for x in range(size))
return pincode
def add_order(self, params):
""" Adds new order with parameters = params to orders and queue.
Parameters:
__________
params : list
A list of parameters of a new order.
Returns
-------
id
Id for new order, equal to self.counter value
pincode
Generated pincode for new order
"""
self.counter += 1
pincode = self.generate_pincode(string.digits + string.ascii_letters, 6)
self.orders[self.counter] = [params, Request_Status.PROCESSING.value, pincode]
self.cached[tuple(params.values())] = self.counter
self.queue.append(self.counter)
return self.counter, pincode
def check_order(self, id):
""" Checks if order with id exists. """
return id in self.orders
def start_scheduler(self, pipe_conn, host, port, slots_num, sharedMemoryId):
""" Executes scheduler, starts an infinite loop for listening pipe connection = pipe_conn and organizing orders execution.
Parameters:
__________
pipe_conn : multiprocessing.connection.Connection
Pipe connection instance for interacting with Net_Interface.
host : str
IP adress or host name for net interface.
port : int
Connection port for net interface.
slots_num : int
A total number of available slots.
"""
logging.debug('start')
try:
gis_path = os.environ['GIS_ROOT']
util_path = gis_path + '/sbin/gis-buffer-renderer'
logging.debug(f'Renderer path ={util_path}')
except Exception as exc:
logging.debug('Could not find GIS_ROOT')
worker = Worker(slots_num)
while True:
# checking new data in Pipe
if pipe_conn.poll():
data = pipe_conn.recv()
logging.debug(f'Scheduler got: {data}')
if data[0] == 0:
# request for new order
logging.debug(f'Scheduler new order {data[1]}')
code = 0
orderId = 0
pincode = 0
if self.validator(data[1]):
param_tuple = tuple(data[1].values())
if param_tuple in self.cached:
logging.debug(f'Order exist, cached data is used')
orderId = self.cached[param_tuple]
pincode = self.orders[self.cached[param_tuple]][2]
else:
orderId, pincode = self.add_order(data[1])
code = 1
pipe_conn.send(((orderId, pincode), code))
elif data[0] == 1:
# request to check order
orderId, pincode = data[1]
logging.debug(f'Scheduler checking order id={orderId}')
if self.check_order(orderId):
logging.debug(f'Status: {self.orders[orderId][1]}')
if self.orders[orderId][2] == pincode:
pipe_conn.send(self.orders[orderId][1])
if self.orders[orderId][1] == Request_Status.READY.value:
#self.orders[orderId][1] = Request_Status.DONE.value
pass
else:
logging.debug(f'Bad pincode {pincode} (not {self.orders[orderId][2]}) for order with id={orderId}')
pipe_conn.send(Request_Status.INVALID_PARAM.value)
else:
logging.debug(f'No order in scheduler with ID {orderId}')
pipe_conn.send(Request_Status.INVALID_PARAM.value)
elif data[0] == 2:
deleted_ids = data[1]
logging.debug(f'Scheduler deletes ids={deleted_ids}')
for i in deleted_ids:
self.cached.pop(tuple(self.orders[i][0].values()))
self.orders.pop(i)
try:
self.queue.remove(i)
except:
pass
pipe_conn.send(True)
continue
else:
#print('No data for scheduler')
pass
# checking queue
#if any of slots are free
if worker.check_free_slot():
#if current_order == False:
if self.queue:
current_order_id = self.queue.pop(0)
logging.debug(f'Current order id {current_order_id}')
params = self.orders[current_order_id][0]
logging.debug(f'{params}')
child = subprocess.Popen([util_path, f'-uhttp://{host}:{port}', f'-o{current_order_id}',f"-x{params['lon']}", f"-y{params['lat']}",
f"-s{params['scale']}", f"-w{params['w']}", f"-h{params['h']}", f"-f{params['format']}", f"-e{Request_Status.RENDER_FAILED.value}", f"-d{sharedMemoryId}"])
worker.fill_slot((current_order_id, child))
logging.debug('popen')
# if some slots are busy
# checking if order is ready
if worker.is_busy():
#if child.poll() != None:
for slot, id in worker.active_slots():
if slot[1].poll() != None:
print('order status ready', Request_Status.READY.value, type(Request_Status.READY.value))
logging.debug(f'Scheduler detects process as ready, return code: {slot[1].returncode}, order status ready {Request_Status.READY.value}')
if slot[1].returncode == 200:
self.orders[slot[0]][1] = Request_Status.READY.value
elif slot[1].returncode == Request_Status.NOMEM.value:
self.orders[slot[0]][1] = Request_Status.NOMEM.value
else:
self.orders[slot[0]][1] = Request_Status.RENDER_FAILED.value
worker.free_slot(id)
time.sleep(0.1)
return 2

156
src/gis-map-server/server.py Исполняемый файл
Просмотреть файл

@ -0,0 +1,156 @@
###############################################################################
# (c) 2011-2022, SWD Embedded Systems Limited, http://www.kpda.ru
###############################################################################
#!/usr/bin/python3 -uB
from net_interface import Net_Interface
from scheduler import Scheduler
from storage import Storage
from utils import get_log_path
import argparse
import shutil
import os
import sys
import time
from multiprocessing import Process, Pipe
import subprocess
class Server:
"""
A class used to represent gis-map-server instance consisting of instances of Interface_Class, Storage_Class and Scheduler_Class classes.
Attributes:
__________
host : str
IP adress or host name for the net interface module.
port : int
Connection port for the net interface module.
buf_size : int
Maximum buffer size in bytes.
slots_num : int
A total number of available slots.
html_path : str
The path to a folder with html pages that are used to responde clients.
Methods:
________
server_init(Interface_Class, Storage_Class, Scheduler_Class)
Initializes server with instances of Interface_Class, Storage_Class and Scheduler_Class classes.
start()
Starts server execution: executes scheduler as a subprocess, executes Interface_Class listening.
"""
def __init__(self, host, port, slots_num, buf_size, html_path, sharedMemoryId):
"""
Parameters:
__________
host : str
IP adress or host name for the net interface module.
port : int
Connection port for the net interface module.
buf_size : int
Maximum buffer size in bytes.
slots_num : int
A total number of available slots.
html_path : str
The path to a folder with html pages that are used to responde clients.
"""
self.port = port
self.host = host
self.buf_size = buf_size
self.slots_num = slots_num
self.html_path = html_path
self.sharedMemoryId = sharedMemoryId
def server_init(self, Interface_Class, Storage_Class, Scheduler_Class):
""" Initializes server with instances of Interface_Class, Storage_Class and Scheduler_Class classes.
Parameters:
__________
Interface_Class : class
A class link to create net_interface.
Storage_Class : class
A class link to create storage.
Scheduler_Class : class
A class link to create scheduler.
"""
self.net_interface = Interface_Class(self.host, self.port)
self.buf_storage = Storage_Class(self.buf_size)
self.scheduler = Scheduler_Class()
def start(self):
""" Starts server execution: executes scheduler as a subprocess, executes Interface_Class listening. Creates Pipe between two processes."""
sched_conn, serv_conn = Pipe()
sched = Process(target=self.scheduler.start_scheduler, args=(sched_conn, self.host, self.port, self.slots_num, self.sharedMemoryId))
sched.start()
# Net_Interface start with access to Buf_Storage and Scheduler
self.net_interface.start_server(serv_conn, self.buf_storage, self.html_path)
def parse_args():
""" Function for parsing program arguments """
parser = argparse.ArgumentParser(description='gis-map-server description:')
parser.add_argument('config_path', type=str,
help='a required string positional argument, path to gis-map-server config')
args = parser.parse_args()
return args
def parse_config(path):
""" Function parses config and return values of required options """
with open(path) as f:
content = f.readlines()
options = dict()
for line in content:
key, val = line.split('=')
options[key] = val.strip()
return options['SERVER_ADDRESS'], int(options['SERVER_PORT']), int(options['SLOTS_NUMBER']), int(options['STORAGE_MAX_SIZE']), options['HTML_PAGES_PATH'], options['GIS_SHID']
if __name__ == '__main__':
try:
args = parse_args()
except Exception as exp:
print(f"Arguments parsing error: {exp}")
sys.exit(1)
try:
address, port, slots_num, buf_size, html_path, sharedMemoryId = parse_config(args.config_path)
except Exception as exp:
print(f"Config parsing error {exp}")
sys.exit(1)
# log folder (re)creating
log_path = get_log_path()
if log_path == None:
print("Could not find GIS_ROOT environment variable")
sys.exit(1)
try:
shutil.rmtree(log_path)
except FileNotFoundError:
pass
except Exception as exc:
print("Could not get an access to log folder", log_path, exc)
sys.exit(1)
try:
os.mkdir(log_path)
except Exception as exc:
print("Could not create log folder", log_path, exc)
sys.exit(1)
try:
subprocess.run(["gis-control", f'-s{sharedMemoryId}'])
except Exception as exc:
print("Could not make data request for sharedMemoryId", sharedMemoryId, exc)
sys.exit(1)
# starting the server
try:
new_server = Server(address, port, slots_num, buf_size, os.environ['GIS_ROOT'] + '/' + html_path, sharedMemoryId)
new_server.server_init(Net_Interface, Storage, Scheduler)
new_server.start()
except KeyboardInterrupt:
print('Server is closed')
sys.exit(0)

89
src/gis-map-server/storage.py Обычный файл
Просмотреть файл

@ -0,0 +1,89 @@
###############################################################################
# (c) 2011-2022, SWD Embedded Systems Limited, http://www.kpda.ru
###############################################################################
#!/usr/bin/python3 -uB
from utils import Request_Status
class Storage():
"""
A class used to represent data storage for buffers obtained from Renderer.
From the storage data is popped from Iterface class when it is requested by the client.
Attributes:
__________
storage : dict
A dictionary used to store buffers. Key: id, val: (data, img_format).
current_size : int
Current size of buffer in bytes. It counts only size of data, not the dictionary element.
buf_size : int
Maximum buffer size in bytes.
Methods:
________
push(id, data, img_format, img_length)
Pushes data to the storage.
pop_by_id(id)
Pops data by id.
"""
def __init__(self, buf_size):
self.storage = dict()
self.current_size = 0
self.buf_size = buf_size
def push(self, id, data, img_format, img_length):
"""Pushes data to the storage.
Parameters:
__________
id : int
An id of the order, whose data will be stored.
data : byte str
Data to store in the storage.
img_format : str
A string which specifies a format of data.
img_length : int
Data size in bytes.
Returns
-------
status
New order status after pushing the data
"""
if id in self.storage:
return Request_Status.INVALID_PARAM.value
if img_length > self.buf_size:
return Request_Status.NOMEM.value
deleted_ids = list()
if self.buf_size < (self.current_size + img_length):
keys = list(self.storage.keys())
for i in keys:
d = self.storage.pop(i)
deleted_ids.append(i)
self.current_size -= len(d[0])
if self.buf_size >= (self.current_size + img_length):
break
self.storage[id] = (data, img_format)
self.current_size += img_length
return Request_Status.READY.value, deleted_ids
def pop_by_id(self, id):
"""
Pops data by id.
Parameters:
__________
id : int
An id of the order, whose data is stored.
Returns
-------
data
Data popped by id
"""
data = self.storage[id]
#self.current_size -= len(data[0])
return data

51
src/gis-map-server/utils.py Обычный файл
Просмотреть файл

@ -0,0 +1,51 @@
###############################################################################
# (c) 2011-2022, SWD Embedded Systems Limited, http://www.kpda.ru
###############################################################################
#!/usr/bin/python3 -uB
from enum import Enum, unique
import os
@unique
class Request_Status(Enum):
# statuses below are saved inside of Scheduler order table
READY = 200
PROCESSING = 202
INVALID_PARAM = 400
DONE = 410
NOMEM = 418
RENDER_FAILED = 500
# codes below are used only to responde to the client
TIMEOUT = 408
REQUEST_FAILED = 520
def get_status_desc(status):
"""Function returns a description for an error code."""
if status == Request_Status.READY.value:
return 'Request is ready'
elif status == Request_Status.PROCESSING.value:
return 'Request is processing'
elif status == Request_Status.INVALID_PARAM.value:
return 'Request has invallid parameters'
elif status == Request_Status.DONE.value:
return 'Request has been already obtained'
elif status == Request_Status.NOMEM.value:
return 'Request is not ready - not enough memory on server'
elif status == Request_Status.RENDER_FAILED.value:
return 'Request is failed - renderer did not finish successfully'
elif status == Request_Status.TIMEOUT.value:
return 'Request status is unknown, timeout error'
elif status == Request_Status.REQUEST_FAILED.value:
return 'Request is failed'
else:
return 'Unknown Error'
def get_log_path():
"""Function returns the log path for gis-map-server."""
try:
path = os.environ['GIS_ROOT']
path += '/data/logs/gis-map-server/'
except:
path = None
return path