package com.trafficaudit.dataimport.service;
|
|
import cn.hutool.core.bean.BeanUtil;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.trafficaudit.common.util.RegionUtil;
|
import com.trafficaudit.dataimport.entity.CityBusMonthly;
|
import com.trafficaudit.dataimport.entity.CityTaxiMonthly;
|
import com.trafficaudit.dataimport.entity.FreightTurnoverImport;
|
import com.trafficaudit.dataimport.entity.H2032EnterpriseMonthly;
|
import com.trafficaudit.dataimport.entity.PassengerEnterpriseMonthly;
|
import com.trafficaudit.dataimport.entity.PassengerIndividualMonthly;
|
import com.trafficaudit.dataimport.entity.WycOrderMonthly;
|
import com.trafficaudit.dataimport.mapper.CityBusMonthlyMapper;
|
import com.trafficaudit.dataimport.mapper.CityTaxiMonthlyMapper;
|
import com.trafficaudit.dataimport.mapper.FreightTurnoverImportMapper;
|
import com.trafficaudit.dataimport.mapper.H2032EnterpriseMonthlyMapper;
|
import com.trafficaudit.dataimport.mapper.PassengerEnterpriseMonthlyMapper;
|
import com.trafficaudit.dataimport.mapper.PassengerIndividualMonthlyMapper;
|
import com.trafficaudit.dataimport.mapper.WycOrderMonthlyMapper;
|
import com.trafficaudit.reportexport.calc.WycSplitCalc;
|
import org.apache.poi.ss.usermodel.BorderStyle;
|
import org.apache.poi.ss.usermodel.Font;
|
import org.apache.poi.ss.usermodel.HorizontalAlignment;
|
import org.apache.poi.xssf.usermodel.XSSFCell;
|
import org.apache.poi.xssf.usermodel.XSSFCellStyle;
|
import org.apache.poi.xssf.usermodel.XSSFRow;
|
import org.apache.poi.xssf.usermodel.XSSFSheet;
|
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
import org.springframework.stereotype.Service;
|
|
import javax.annotation.Resource;
|
import java.io.ByteArrayOutputStream;
|
import java.io.IOException;
|
import java.util.ArrayList;
|
import java.util.Arrays;
|
import java.util.Collections;
|
import java.util.Comparator;
|
import java.util.LinkedHashMap;
|
import java.util.LinkedHashSet;
|
import java.util.List;
|
import java.util.Map;
|
import java.util.Set;
|
|
/**
|
* 数据查看 - 按市州汇总(任意起止月)。
|
*
|
* 口径(2026-09-22 与用户确认):
|
* 1) 流量类列(客运量/周转量/货运量/订单数)跨期求和;
|
* 2) 时点类列(车辆数、企业数、站点数)不参与汇总,不出现在结果里;
|
* 3) 比率类列按累计口径重算:累计同比 = 本期累计 ÷ 上年同期累计 − 1;平均运距 = 累计周转量 ÷ 累计客运量;
|
* 4) 排名默认留空,可由前端选择"按累计值重排";
|
* 5) 结果 = 17 市州(RegionUtil 规范名/顺序)+ 全省合计行。
|
*/
|
@Service
|
public class CityRollupService {
|
|
public static final String TOTAL_CITY = "全省合计";
|
|
@Resource
|
private CityBusMonthlyMapper cityBusMapper;
|
@Resource
|
private CityTaxiMonthlyMapper cityTaxiMapper;
|
@Resource
|
private H2032EnterpriseMonthlyMapper h2032Mapper;
|
@Resource
|
private PassengerEnterpriseMonthlyMapper h2031Mapper;
|
@Resource
|
private PassengerIndividualMonthlyMapper individualMapper;
|
@Resource
|
private WycOrderMonthlyMapper wycOrderMapper;
|
@Resource
|
private FreightTurnoverImportMapper freightTurnoverMapper;
|
@Resource
|
private WycSplitCalc wycSplitCalc;
|
|
/** 汇总列定义。kind:FLOW 求和 / AVG 分子÷分母 / YOY 本期累计↔上年同期累计 / YOYS 同行去年字段 / RANK 按某输出列重排名次 */
|
private static class Col {
|
final String prop;
|
final String label;
|
final int width;
|
final String kind;
|
final String src;
|
final String den;
|
final String last;
|
final String rankBy;
|
|
Col(String prop, String label, int width, String kind, String src, String den, String last, String rankBy) {
|
this.prop = prop;
|
this.label = label;
|
this.width = width;
|
this.kind = kind;
|
this.src = src;
|
this.den = den;
|
this.last = last;
|
this.rankBy = rankBy;
|
}
|
}
|
|
private static Col flow(String prop, String label, int width, String src) {
|
return new Col(prop, label, width, "FLOW", src, null, null, null);
|
}
|
|
private static Col avg(String prop, String label, String src, String den) {
|
return new Col(prop, label, 130, "AVG", src, den, null, null);
|
}
|
|
private static Col yoy(String prop, String label, String src) {
|
return new Col(prop, label, 150, "YOY", src, null, null, null);
|
}
|
|
private static Col yoys(String prop, String label, String src, String last) {
|
return new Col(prop, label, 150, "YOYS", src, null, last, null);
|
}
|
|
private static Col rank(String prop, String label, String rankBy) {
|
return new Col(prop, label, 110, "RANK", null, null, null, rankBy);
|
}
|
|
private static final Map<String, String> TYPE_LABELS = new LinkedHashMap<>();
|
private static final Map<String, List<Col>> SPECS = new LinkedHashMap<>();
|
|
static {
|
TYPE_LABELS.put("cityBus", "城市公共交通(按市州汇总)");
|
SPECS.put("cityBus", Arrays.asList(
|
flow("passengerVolume", "客运量(万人次)", 140, "passengerVolume"),
|
flow("turnover", "旅客周转量(万人公里)", 180, "turnover"),
|
flow("passengerCity", "城市内客运量(万人次)", 170, "passengerCity"),
|
flow("turnoverCity", "城市内周转量(万人公里)", 190, "turnoverCity"),
|
flow("passengerChengxiang", "城际城乡客运量(万人次)", 190, "passengerChengxiang"),
|
flow("turnoverChengxiang", "城际城乡周转量(万人公里)", 210, "turnoverChengxiang"),
|
avg("avgDistance", "平均运距(公里)", "turnover", "passengerVolume"),
|
yoy("passengerYoy", "客运量累计同比(%)", "passengerVolume"),
|
rank("passengerRank", "客运量排名", "passengerVolume")));
|
|
TYPE_LABELS.put("cityTaxi", "巡游出租汽车(按市州汇总)");
|
SPECS.put("cityTaxi", Arrays.asList(
|
flow("passengerVolume", "客运量(万人次)", 140, "passengerVolume"),
|
flow("turnover", "旅客周转量(万人公里)", 180, "turnover"),
|
flow("passengerCity", "城市内客运量(万人次)", 170, "passengerCity"),
|
flow("turnoverCity", "城市内周转量(万人公里)", 190, "turnoverCity"),
|
flow("tripTotal", "载客车次总数(车次)", 170, "tripTotal"),
|
flow("tripCity", "载客车次_城市内(车次)", 190, "tripCity"),
|
avg("avgDistance", "平均运距(公里)", "turnover", "passengerVolume"),
|
yoy("passengerYoy", "客运量累计同比(%)", "passengerVolume"),
|
rank("passengerRank", "客运量排名", "passengerVolume")));
|
|
TYPE_LABELS.put("h2032", "道路货物运输企业(按市州汇总)");
|
SPECS.put("h2032", Arrays.asList(
|
flow("freightTotal", "货运量(吨)", 150, "freightTotal"),
|
flow("turnoverTotal", "货运周转量(吨公里)", 180, "turnoverTotal"),
|
avg("avgDistance", "平均运距(公里)", "turnoverTotal", "freightTotal"),
|
yoy("freightYoy", "货运量累计同比(%)", "freightTotal"),
|
rank("freightRank", "货运量排名", "freightTotal"),
|
rank("turnoverRank", "周转量排名", "turnoverTotal")));
|
|
TYPE_LABELS.put("h2031", "公路旅客运输企业(按市州汇总)");
|
SPECS.put("h2031", Arrays.asList(
|
flow("passengerTotal", "客运量(人)", 140, "passengerTotal"),
|
flow("turnoverTotal", "旅客周转量(人公里)", 180, "turnoverTotal"),
|
avg("avgDistance", "平均运距(公里)", "turnoverTotal", "passengerTotal"),
|
yoy("passengerYoy", "客运量累计同比(%)", "passengerTotal"),
|
rank("passengerRank", "客运量排名", "passengerTotal")));
|
|
TYPE_LABELS.put("passengerIndividual", "个体客运量/周转量(按市州汇总)");
|
SPECS.put("passengerIndividual", Arrays.asList(
|
flow("passengerCount", "个体客运量(人)", 150, "passengerCount"),
|
flow("turnover", "个体旅客周转量(人公里)", 190, "turnover"),
|
yoy("passengerYoy", "客运量累计同比(%)", "passengerCount")));
|
|
TYPE_LABELS.put("wycOrder", "网约车(按市州、拆分口径汇总)");
|
SPECS.put("wycOrder", Arrays.asList(
|
flow("orderCount", "订单数(单)", 150, "orderCount"),
|
flow("pax", "客运量(万人次)", 140, "pax"),
|
flow("turnover", "旅客周转量(万人公里)", 180, "turnover"),
|
flow("cityPax", "城市内客运量(万人次)", 170, "cityPax"),
|
flow("cityTurnover", "城市内旅客周转量(万人公里)", 210, "cityTurnover"),
|
yoy("paxYoy", "客运量累计同比(%)", "pax"),
|
rank("paxRank", "客运量排名", "pax")));
|
|
TYPE_LABELS.put("freightTurnover", "货运量周转量(按市州汇总)");
|
SPECS.put("freightTurnover", Arrays.asList(
|
flow("freight", "货运量(万吨)", 140, "freightM"),
|
flow("turnover", "周转量(万吨公里)", 170, "turnoverM"),
|
yoys("freightYoy", "货运量累计同比(%)", "freightM", "lastFreightM"),
|
yoys("turnoverYoy", "周转量累计同比(%)", "turnoverM", "lastTurnoverM"),
|
rank("freightRank", "货运量排名", "freight"),
|
rank("turnoverRank", "周转量排名", "turnover")));
|
}
|
|
public List<Map<String, Object>> supportedTypes() {
|
List<Map<String, Object>> list = new ArrayList<>();
|
for (Map.Entry<String, String> e : TYPE_LABELS.entrySet()) {
|
Map<String, Object> m = new LinkedHashMap<>();
|
m.put("type", e.getKey());
|
m.put("label", e.getValue());
|
list.add(m);
|
}
|
return list;
|
}
|
|
/** 按市州汇总 */
|
public Map<String, Object> summary(String type, String from, String to, boolean recomputeRank) {
|
List<Col> cols = SPECS.get(type);
|
if (cols == null) {
|
throw new RuntimeException("该数据类型暂不支持按市州汇总:" + type);
|
}
|
String f = trimPeriod(from);
|
String t = trimPeriod(to);
|
if (f == null || t == null) {
|
throw new RuntimeException("请先选择起止月(例如 2026-01 ~ 2026-08)");
|
}
|
if (f.compareTo(t) > 0) {
|
String tmp = f;
|
f = t;
|
t = tmp;
|
}
|
String lastFrom = yearBefore(f);
|
String lastTo = yearBefore(t);
|
|
Map<String, Map<String, Double>> cur = sumByCity(longRows(type, f, t), cols);
|
Map<String, Map<String, Double>> last = sumByCity(longRows(type, lastFrom, lastTo), cols);
|
|
List<Map<String, Object>> records = new ArrayList<>();
|
for (String city : RegionUtil.cityList()) {
|
records.add(outRow(cols, cur.get(city), last.get(city), city));
|
}
|
records.add(outRow(cols, mergeAll(cur), mergeAll(last), TOTAL_CITY));
|
if (recomputeRank) {
|
applyRank(records, cols);
|
}
|
|
List<Map<String, Object>> columns = new ArrayList<>();
|
columns.add(colMeta("city", "市州", 120));
|
for (Col c : cols) {
|
columns.add(colMeta(c.prop, c.label, c.width));
|
}
|
Map<String, Object> res = new LinkedHashMap<>();
|
res.put("type", type);
|
res.put("label", TYPE_LABELS.get(type));
|
res.put("from", f);
|
res.put("to", t);
|
res.put("lastFrom", lastFrom);
|
res.put("lastTo", lastTo);
|
res.put("rankMode", recomputeRank ? "recompute" : "blank");
|
res.put("columns", columns);
|
res.put("records", records);
|
return res;
|
}
|
|
/** 导出为 xlsx(数值保留全精度,显示格式 2 位小数 / 百分比) */
|
public byte[] exportExcel(String type, String from, String to, boolean recomputeRank) throws IOException {
|
Map<String, Object> data = summary(type, from, to, recomputeRank);
|
@SuppressWarnings("unchecked")
|
List<Map<String, Object>> columns = (List<Map<String, Object>>) data.get("columns");
|
@SuppressWarnings("unchecked")
|
List<Map<String, Object>> records = (List<Map<String, Object>>) data.get("records");
|
String label = String.valueOf(data.get("label"));
|
String title = label + " " + data.get("from") + " ~ " + data.get("to") + "(累计)";
|
|
try (XSSFWorkbook wb = new XSSFWorkbook(); ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
|
XSSFSheet sheet = wb.createSheet("按市州汇总");
|
Font bold = wb.createFont();
|
bold.setBold(true);
|
XSSFCellStyle titleStyle = wb.createCellStyle();
|
titleStyle.setFont(bold);
|
XSSFCellStyle headStyle = wb.createCellStyle();
|
headStyle.setFont(bold);
|
headStyle.setAlignment(HorizontalAlignment.CENTER);
|
headStyle.setBorderBottom(BorderStyle.THIN);
|
XSSFCellStyle numStyle = wb.createCellStyle();
|
numStyle.setDataFormat(wb.createDataFormat().getFormat("0.00"));
|
XSSFCellStyle pctStyle = wb.createCellStyle();
|
pctStyle.setDataFormat(wb.createDataFormat().getFormat("0.00%"));
|
|
XSSFRow titleRow = sheet.createRow(0);
|
XSSFCell titleCell = titleRow.createCell(0);
|
titleCell.setCellValue(title);
|
titleCell.setCellStyle(titleStyle);
|
|
XSSFRow headRow = sheet.createRow(1);
|
for (int i = 0; i < columns.size(); i++) {
|
XSSFCell cell = headRow.createCell(i);
|
cell.setCellValue(String.valueOf(columns.get(i).get("label")));
|
cell.setCellStyle(headStyle);
|
}
|
int r = 2;
|
for (Map<String, Object> rec : records) {
|
XSSFRow row = sheet.createRow(r++);
|
for (int i = 0; i < columns.size(); i++) {
|
String prop = String.valueOf(columns.get(i).get("prop"));
|
String colLabel = String.valueOf(columns.get(i).get("label"));
|
Object v = rec.get(prop);
|
XSSFCell cell = row.createCell(i);
|
if (v instanceof Number) {
|
cell.setCellValue(((Number) v).doubleValue());
|
cell.setCellStyle(colLabel.contains("同比") ? pctStyle : numStyle);
|
} else {
|
cell.setCellValue(v == null ? "" : String.valueOf(v));
|
}
|
}
|
}
|
for (int i = 0; i < columns.size(); i++) {
|
sheet.setColumnWidth(i, Math.min(40, Math.max(10, Integer.parseInt(String.valueOf(columns.get(i).get("width"))) / 7)) * 256);
|
}
|
wb.write(bos);
|
return bos.toByteArray();
|
}
|
}
|
|
// ==================== 内部实现 ====================
|
|
private static String trimPeriod(String p) {
|
if (p == null) {
|
return null;
|
}
|
String s = p.trim();
|
if (s.length() >= 7) {
|
return s.substring(0, 7);
|
}
|
return s.isEmpty() ? null : s;
|
}
|
|
private static String yearBefore(String period) {
|
int year = Integer.parseInt(period.substring(0, 4)) - 1;
|
return year + period.substring(4);
|
}
|
|
private static Map<String, Object> colMeta(String prop, String label, int width) {
|
Map<String, Object> m = new LinkedHashMap<>();
|
m.put("prop", prop);
|
m.put("label", label);
|
m.put("width", width);
|
return m;
|
}
|
|
/** 只有 17 个规范市州名才算市州;"湖北省""全省"等合计行返回 null(避免重复计入) */
|
private String cityOrNull(String name) {
|
if (name == null) {
|
return null;
|
}
|
String normalized = RegionUtil.normalizeCityName(name);
|
return normalized != null && RegionUtil.cityList().contains(normalized) ? normalized : null;
|
}
|
|
private String cityOfCode(String regionCode) {
|
if (regionCode == null || regionCode.trim().isEmpty()) {
|
return null;
|
}
|
return RegionUtil.cityByCode(regionCode.trim());
|
}
|
|
private static double nz(Double v) {
|
return v == null ? 0.0 : v;
|
}
|
|
private static double num(Object v) {
|
if (v instanceof Number) {
|
return ((Number) v).doubleValue();
|
}
|
if (v instanceof String) {
|
try {
|
return Double.parseDouble(((String) v).trim());
|
} catch (Exception ignore) {
|
return 0.0;
|
}
|
}
|
return 0.0;
|
}
|
|
/** 取某类型在 [from,to] 的"长表"行:每行含 _city 与参与汇总的源字段 */
|
private List<Map<String, Object>> longRows(String type, String from, String to) {
|
List<Map<String, Object>> rows = new ArrayList<>();
|
switch (type) {
|
case "cityBus": {
|
List<CityBusMonthly> list = cityBusMapper.selectList(new LambdaQueryWrapper<CityBusMonthly>()
|
.ge(CityBusMonthly::getReportPeriod, from).le(CityBusMonthly::getReportPeriod, to));
|
for (CityBusMonthly e : list) {
|
String city = e.getCity() != null ? cityOrNull(e.getCity()) : cityOfCode(e.getRegionCode());
|
if (city == null) {
|
continue;
|
}
|
Map<String, Object> m = BeanUtil.beanToMap(e);
|
m.put("_city", city);
|
rows.add(m);
|
}
|
break;
|
}
|
case "cityTaxi": {
|
List<CityTaxiMonthly> list = cityTaxiMapper.selectList(new LambdaQueryWrapper<CityTaxiMonthly>()
|
.ge(CityTaxiMonthly::getReportPeriod, from).le(CityTaxiMonthly::getReportPeriod, to));
|
for (CityTaxiMonthly e : list) {
|
String city = e.getCity() != null ? cityOrNull(e.getCity()) : cityOfCode(e.getRegionCode());
|
if (city == null) {
|
continue;
|
}
|
Map<String, Object> m = BeanUtil.beanToMap(e);
|
m.put("_city", city);
|
rows.add(m);
|
}
|
break;
|
}
|
case "h2032": {
|
List<H2032EnterpriseMonthly> list = h2032Mapper.selectList(new LambdaQueryWrapper<H2032EnterpriseMonthly>()
|
.ge(H2032EnterpriseMonthly::getReportPeriod, from).le(H2032EnterpriseMonthly::getReportPeriod, to));
|
for (H2032EnterpriseMonthly e : list) {
|
String city = cityOfCode(e.getRegionCode());
|
if (city == null) {
|
continue;
|
}
|
Map<String, Object> m = BeanUtil.beanToMap(e);
|
m.put("_city", city);
|
rows.add(m);
|
}
|
break;
|
}
|
case "h2031": {
|
List<PassengerEnterpriseMonthly> list = h2031Mapper.selectList(new LambdaQueryWrapper<PassengerEnterpriseMonthly>()
|
.ge(PassengerEnterpriseMonthly::getReportPeriod, from).le(PassengerEnterpriseMonthly::getReportPeriod, to));
|
for (PassengerEnterpriseMonthly e : list) {
|
String city = cityOfCode(e.getRegionCode());
|
if (city == null) {
|
continue;
|
}
|
Map<String, Object> m = BeanUtil.beanToMap(e);
|
m.put("_city", city);
|
rows.add(m);
|
}
|
break;
|
}
|
case "passengerIndividual": {
|
List<PassengerIndividualMonthly> list = individualMapper.selectList(new LambdaQueryWrapper<PassengerIndividualMonthly>()
|
.ge(PassengerIndividualMonthly::getReportPeriod, from).le(PassengerIndividualMonthly::getReportPeriod, to));
|
for (PassengerIndividualMonthly e : list) {
|
String city = cityOfCode(e.getRegionCode());
|
if (city == null) {
|
continue;
|
}
|
Map<String, Object> m = BeanUtil.beanToMap(e);
|
m.put("_city", city);
|
rows.add(m);
|
}
|
break;
|
}
|
case "wycOrder": {
|
List<WycOrderMonthly> list = wycOrderMapper.selectList(new LambdaQueryWrapper<WycOrderMonthly>()
|
.ge(WycOrderMonthly::getReportPeriod, from).le(WycOrderMonthly::getReportPeriod, to));
|
List<String> cities = RegionUtil.cityList();
|
for (WycOrderMonthly e : list) {
|
Map<String, WycSplitCalc.WycMetrics> byCity = null;
|
try {
|
byCity = wycSplitCalc.calc(e.getReportPeriod()).getByCity();
|
} catch (Exception ignore) {
|
// 拆分输入不全时只汇总订单数
|
}
|
double[] orders = {nz(e.getOrderWuhan()), nz(e.getOrderHuangshi()), nz(e.getOrderShiyan()),
|
nz(e.getOrderYichang()), nz(e.getOrderXiangyang()), nz(e.getOrderEzhou()), nz(e.getOrderJingmen()),
|
nz(e.getOrderXiaogan()), nz(e.getOrderJingzhou()), nz(e.getOrderHuanggang()), nz(e.getOrderXianning()),
|
nz(e.getOrderSuizhou()), nz(e.getOrderEnshi()), nz(e.getOrderXiantao()), nz(e.getOrderQianjiang()),
|
nz(e.getOrderTianmen()), nz(e.getOrderShennong())};
|
for (int i = 0; i < cities.size() && i < orders.length; i++) {
|
Map<String, Object> m = new LinkedHashMap<>();
|
m.put("_city", cities.get(i));
|
m.put("orderCount", orders[i]);
|
WycSplitCalc.WycMetrics mt = byCity == null ? null : byCity.get(cities.get(i));
|
if (mt != null) {
|
m.put("pax", mt.getTotalPax());
|
m.put("turnover", mt.getTotalTurnover());
|
m.put("cityPax", mt.getCityPax());
|
m.put("cityTurnover", mt.getCityTurnover());
|
}
|
rows.add(m);
|
}
|
}
|
break;
|
}
|
case "freightTurnover": {
|
// 该表每行含"年初至当期"的各月列(行内累计),故累计只用 to 那一行、对区间月份求和;
|
// 且"湖北省"行等于 17 市州之和,必须排除,否则重复计入。
|
List<FreightTurnoverImport> list = freightTurnoverMapper.selectList(new LambdaQueryWrapper<FreightTurnoverImport>()
|
.ge(FreightTurnoverImport::getReportPeriod, from)
|
.le(FreightTurnoverImport::getReportPeriod, to)
|
.orderByDesc(FreightTurnoverImport::getReportPeriod));
|
if (list.isEmpty()) {
|
break;
|
}
|
// 该表每行是"年初至当期"的行内累计,取区间内最新一期那一行;结束月无数据时自动回退到最新有数月份
|
String latestPeriod = list.get(0).getReportPeriod();
|
list = new ArrayList<>();
|
for (FreightTurnoverImport e : freightTurnoverMapper.selectList(new LambdaQueryWrapper<FreightTurnoverImport>()
|
.eq(FreightTurnoverImport::getReportPeriod, latestPeriod))) {
|
list.add(e);
|
}
|
int m1 = Integer.parseInt(from.substring(5, 7));
|
int m2 = Integer.parseInt(latestPeriod.substring(5, 7));
|
if (m2 < m1) {
|
break;
|
}
|
for (FreightTurnoverImport e : list) {
|
String city = cityOrNull(e.getRegionName());
|
if (city == null) {
|
continue;
|
}
|
Map<String, Object> src = BeanUtil.beanToMap(e);
|
double freight = 0, turnover = 0, lastFreight = 0, lastTurnover = 0;
|
for (int mm = m1; mm <= m2; mm++) {
|
freight += num(src.get(String.format("freightM%02d", mm)));
|
turnover += num(src.get(String.format("turnoverM%02d", mm)));
|
lastFreight += num(src.get(String.format("lastFreightM%02d", mm)));
|
lastTurnover += num(src.get(String.format("lastTurnoverM%02d", mm)));
|
}
|
Map<String, Object> m = new LinkedHashMap<>();
|
m.put("_city", city);
|
m.put("freightM", freight);
|
m.put("turnoverM", turnover);
|
m.put("lastFreightM", lastFreight);
|
m.put("lastTurnoverM", lastTurnover);
|
rows.add(m);
|
}
|
break;
|
}
|
default:
|
break;
|
}
|
return rows;
|
}
|
|
/** 按市州累计各列用到的源字段 */
|
private Map<String, Map<String, Double>> sumByCity(List<Map<String, Object>> rows, List<Col> cols) {
|
Map<String, Map<String, Double>> acc = new LinkedHashMap<>();
|
for (Map<String, Object> row : rows) {
|
Object cityObj = row.get("_city");
|
if (cityObj == null) {
|
continue;
|
}
|
String city = String.valueOf(cityObj);
|
Map<String, Double> sums = acc.computeIfAbsent(city, k -> new LinkedHashMap<>());
|
Set<String> keys = new LinkedHashSet<>();
|
for (Col c : cols) {
|
if (c.src != null) {
|
keys.add(c.src);
|
}
|
if (c.den != null) {
|
keys.add(c.den);
|
}
|
if (c.last != null) {
|
keys.add(c.last);
|
}
|
}
|
for (String key : keys) {
|
sums.merge(key, num(row.get(key)), Double::sum);
|
}
|
}
|
return acc;
|
}
|
|
private Map<String, Double> mergeAll(Map<String, Map<String, Double>> acc) {
|
Map<String, Double> total = new LinkedHashMap<>();
|
for (Map<String, Double> sums : acc.values()) {
|
for (Map.Entry<String, Double> e : sums.entrySet()) {
|
total.merge(e.getKey(), e.getValue(), Double::sum);
|
}
|
}
|
return total;
|
}
|
|
private Map<String, Object> outRow(List<Col> cols, Map<String, Double> cur, Map<String, Double> last, String city) {
|
Map<String, Object> row = new LinkedHashMap<>();
|
row.put("city", city);
|
for (Col c : cols) {
|
Double value = null;
|
switch (c.kind) {
|
case "FLOW": {
|
value = cur == null ? null : cur.get(c.src);
|
break;
|
}
|
case "AVG": {
|
double den = cur == null ? 0.0 : num(cur.get(c.den));
|
double nume = cur == null ? 0.0 : num(cur.get(c.src));
|
value = den > 0 ? nume / den : null;
|
break;
|
}
|
case "YOY": {
|
double base = last == null ? 0.0 : num(last.get(c.src));
|
double now = cur == null ? 0.0 : num(cur.get(c.src));
|
value = base > 0 ? now / base - 1 : null;
|
break;
|
}
|
case "YOYS": {
|
double base = cur == null ? 0.0 : num(cur.get(c.last));
|
double now = cur == null ? 0.0 : num(cur.get(c.src));
|
value = base > 0 ? now / base - 1 : null;
|
break;
|
}
|
default: {
|
value = null; // RANK: 由 applyRank 填
|
}
|
}
|
row.put(c.prop, value);
|
}
|
return row;
|
}
|
|
/** 按累计值重新排名(1 = 最大);全省合计行不排名 */
|
private void applyRank(List<Map<String, Object>> records, List<Col> cols) {
|
List<Map<String, Object>> cities = new ArrayList<>();
|
for (Map<String, Object> r : records) {
|
if (!TOTAL_CITY.equals(r.get("city"))) {
|
cities.add(r);
|
}
|
}
|
for (Col c : cols) {
|
if (!"RANK".equals(c.kind)) {
|
continue;
|
}
|
List<Map<String, Object>> sorted = new ArrayList<>(cities);
|
sorted.sort(Comparator.comparingDouble((Map<String, Object> r) -> {
|
Object v = r.get(c.rankBy);
|
return v instanceof Number ? ((Number) v).doubleValue() : Double.NEGATIVE_INFINITY;
|
}).reversed());
|
for (int i = 0; i < sorted.size(); i++) {
|
Object v = sorted.get(i).get(c.rankBy);
|
sorted.get(i).put(c.prop, v instanceof Number ? (i + 1) : null);
|
}
|
}
|
}
|
}
|